Merge tag '6.2.1' into unstable
Former-commit-id: bfed57e3e0edaa724b9d060a6bb8edc5a6de65fa
This commit is contained in:
commit
fe8efa916b
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,20 +1,24 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Help us improve KeyDB by reporting a bug
|
||||||
title: ''
|
title: '[BUG]'
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
** Log Files **
|
A short description of the bug.
|
||||||
These should be KeyDB logs, not syslogs or logs from your container manager. If you are reporting a crash there will be a line in your log stating:
|
|
||||||
"=== KEYDB BUG REPORT START: Cut & paste starting from here ==="
|
|
||||||
|
|
||||||
Please copy everything after this line.
|
**To reproduce**
|
||||||
|
|
||||||
**To Reproduce**
|
Steps to reproduce the behavior and/or a minimal code sample.
|
||||||
Do you know how to reproduce this? If so please provide repro steps.
|
|
||||||
|
**Expected behavior**
|
||||||
|
|
||||||
|
A description of what you expected to happen.
|
||||||
|
|
||||||
|
**Additional information**
|
||||||
|
|
||||||
|
Any additional information that is relevant to the problem.
|
||||||
|
20
.github/ISSUE_TEMPLATE/crash_report.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/crash_report.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Crash report
|
||||||
|
about: Submit a crash report
|
||||||
|
title: '[CRASH]'
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aditional information**
|
||||||
|
|
||||||
|
1. OS distribution and version
|
||||||
|
2. Steps to reproduce (if any)
|
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,20 +1,24 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea for this project
|
about: Suggest a feature for KeyDB
|
||||||
title: ''
|
title: '[NEW]'
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**The problem/use-case that the feature addresses**
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
A description of the problem that the feature will solve, or the use-case with which the feature will be used.
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
**Description of the feature**
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
A description of what you want to happen.
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
|
**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.
|
||||||
|
8
.github/ISSUE_TEMPLATE/other_stuff.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE/other_stuff.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: Other
|
||||||
|
about: Can't find the right issue type? Use this one!
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
21
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
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)
|
||||||
|
* [the irc channel #redis](http://webchat.freenode.net/?channels=redis) on freenode
|
||||||
|
|
||||||
|
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 :)
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||||
make BUILD_TLS=yes -j2
|
make REDIS_CFLAGS='-Werror' BUILD_TLS=yes -j2
|
||||||
- name: gen-cert
|
- name: gen-cert
|
||||||
run: ./utils/gen-test-certs.sh
|
run: ./utils/gen-test-certs.sh
|
||||||
- name: test-tls
|
- name: test-tls
|
||||||
@ -44,7 +44,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: make
|
- name: make
|
||||||
run: make -j2
|
run: make REDIS_CFLAGS='-Werror' -j2
|
||||||
|
|
||||||
build-libc-malloc:
|
build-libc-malloc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -54,5 +54,5 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||||
make MALLOC=libc -j2
|
make REDIS_CFLAGS='-Werror' MALLOC=libc -j2
|
||||||
|
|
||||||
|
180
.github/workflows/daily.yml
vendored
180
.github/workflows/daily.yml
vendored
@ -1,180 +0,0 @@
|
|||||||
name: Daily
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
# any PR to a release branch.
|
|
||||||
- '[0-9].[0-9]'
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
test-ubuntu-jemalloc:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'redis/redis'
|
|
||||||
timeout-minutes: 14400
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: make
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
|
||||||
make
|
|
||||||
- name: test
|
|
||||||
run: |
|
|
||||||
sudo apt-get install tcl8.5
|
|
||||||
./runtest --accurate --verbose
|
|
||||||
- name: module api test
|
|
||||||
run: ./runtest-moduleapi --verbose
|
|
||||||
- name: sentinel tests
|
|
||||||
run: ./runtest-sentinel
|
|
||||||
- name: cluster tests
|
|
||||||
run: ./runtest-cluster
|
|
||||||
|
|
||||||
test-ubuntu-libc-malloc:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'redis/redis'
|
|
||||||
timeout-minutes: 14400
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: make
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
|
||||||
make MALLOC=libc
|
|
||||||
- name: test
|
|
||||||
run: |
|
|
||||||
sudo apt-get install tcl8.5
|
|
||||||
./runtest --accurate --verbose
|
|
||||||
- name: module api test
|
|
||||||
run: ./runtest-moduleapi --verbose
|
|
||||||
- name: sentinel tests
|
|
||||||
run: ./runtest-sentinel
|
|
||||||
- name: cluster tests
|
|
||||||
run: ./runtest-cluster
|
|
||||||
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'redis/redis'
|
|
||||||
timeout-minutes: 14400
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: make
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
|
||||||
make BUILD_TLS=yes -j2
|
|
||||||
- name: "test (tls)"
|
|
||||||
run: |
|
|
||||||
sudo apt-get install tcl8.5 tcl-tls
|
|
||||||
./utils/gen-test-certs.sh
|
|
||||||
./runtest --accurate --verbose --tls
|
|
||||||
- name: test
|
|
||||||
run: ./runtest --accurate --verbose
|
|
||||||
- name: module api test (tls)
|
|
||||||
run: ./runtest-moduleapi --verbose --tls
|
|
||||||
|
|
||||||
test-ubuntu-arm:
|
|
||||||
runs-on: [self-hosted, linux, arm]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: make
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
|
||||||
make -j4
|
|
||||||
- name: test
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y install tcl8.5
|
|
||||||
./runtest --clients 2 --verbose
|
|
||||||
- name: module tests
|
|
||||||
run: |
|
|
||||||
./runtest-moduleapi
|
|
||||||
|
|
||||||
test-valgrind:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'redis/redis'
|
|
||||||
timeout-minutes: 14400
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: make
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
|
||||||
make valgrind
|
|
||||||
- name: test
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install tcl8.5 valgrind -y
|
|
||||||
./runtest --valgrind --verbose --clients 1
|
|
||||||
- name: module api test
|
|
||||||
run: ./runtest-moduleapi --valgrind --verbose --clients 1
|
|
||||||
|
|
||||||
test-centos7-jemalloc:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'redis/redis'
|
|
||||||
container: centos:7
|
|
||||||
timeout-minutes: 14400
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: make
|
|
||||||
run: |
|
|
||||||
yum -y install centos-release-scl
|
|
||||||
yum -y install devtoolset-7
|
|
||||||
scl enable devtoolset-7 "make"
|
|
||||||
- name: test
|
|
||||||
run: |
|
|
||||||
yum -y install tcl
|
|
||||||
./runtest --accurate --verbose
|
|
||||||
- name: module api test
|
|
||||||
run: ./runtest-moduleapi --verbose
|
|
||||||
- name: sentinel tests
|
|
||||||
run: ./runtest-sentinel
|
|
||||||
- name: cluster tests
|
|
||||||
run: ./runtest-cluster
|
|
||||||
|
|
||||||
test-centos7-tls:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'redis/redis'
|
|
||||||
container: centos:7
|
|
||||||
timeout-minutes: 14400
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: make
|
|
||||||
run: |
|
|
||||||
yum -y install centos-release-scl epel-release
|
|
||||||
yum -y install devtoolset-7 openssl-devel openssl
|
|
||||||
scl enable devtoolset-7 "make BUILD_TLS=yes"
|
|
||||||
- name: test
|
|
||||||
run: |
|
|
||||||
yum -y install tcl tcltls
|
|
||||||
./utils/gen-test-certs.sh
|
|
||||||
./runtest --accurate --verbose --tls
|
|
||||||
./runtest --accurate --verbose
|
|
||||||
- name: module api test
|
|
||||||
run: |
|
|
||||||
./runtest-moduleapi --verbose --tls
|
|
||||||
./runtest-moduleapi --verbose
|
|
||||||
- name: sentinel tests
|
|
||||||
run: |
|
|
||||||
./runtest-sentinel --tls
|
|
||||||
./runtest-sentinel
|
|
||||||
- name: cluster tests
|
|
||||||
run: |
|
|
||||||
./runtest-cluster --tls
|
|
||||||
./runtest-cluster
|
|
||||||
|
|
||||||
test-macos-latest:
|
|
||||||
runs-on: macos-latest
|
|
||||||
if: github.repository == 'redis/redis'
|
|
||||||
timeout-minutes: 14400
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: make
|
|
||||||
run: make
|
|
||||||
- name: test
|
|
||||||
run: |
|
|
||||||
./runtest --accurate --verbose --no-latency
|
|
||||||
- name: module api test
|
|
||||||
run: ./runtest-moduleapi --verbose
|
|
||||||
- name: sentinel tests
|
|
||||||
run: ./runtest-sentinel
|
|
||||||
- name: cluster tests
|
|
||||||
run: ./runtest-cluster
|
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -58,3 +58,4 @@ Makefile.dep
|
|||||||
.ccls
|
.ccls
|
||||||
.ccls-cache/*
|
.ccls-cache/*
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
redis.code-workspace
|
||||||
|
3280
00-RELEASENOTES
3280
00-RELEASENOTES
File diff suppressed because it is too large
Load Diff
96
CONDUCT
Normal file
96
CONDUCT
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
Contributor 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.
|
3
COPYING
3
COPYING
@ -1,5 +1,6 @@
|
|||||||
Copyright (c) 2006-2020, Salvatore Sanfilippo
|
Copyright (c) 2006-2020, Salvatore Sanfilippo
|
||||||
Copyright (C) 2019-2020, John Sully
|
Copyright (C) 2019-2021, John Sully
|
||||||
|
Copyright (C) 2020-2021, EQ Alpha Technology Ltd.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
19
README.md
19
README.md
@ -118,6 +118,13 @@ installed):
|
|||||||
% ./runtest --tls
|
% ./runtest --tls
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Fixing build problems with dependencies or cached build options
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@ -169,6 +176,18 @@ To compile against jemalloc on Mac OS X systems, use:
|
|||||||
|
|
||||||
% make MALLOC=jemalloc
|
% 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
|
Verbose build
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
13
deps/Makefile
vendored
13
deps/Makefile
vendored
@ -37,6 +37,7 @@ distclean:
|
|||||||
-(cd linenoise && $(MAKE) clean) > /dev/null || true
|
-(cd linenoise && $(MAKE) clean) > /dev/null || true
|
||||||
-(cd lua && $(MAKE) clean) > /dev/null || true
|
-(cd lua && $(MAKE) clean) > /dev/null || true
|
||||||
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
|
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
|
||||||
|
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
|
||||||
-(rm -f .make-*)
|
-(rm -f .make-*)
|
||||||
|
|
||||||
.PHONY: distclean
|
.PHONY: distclean
|
||||||
@ -62,18 +63,24 @@ memkind:
|
|||||||
cd memkind && $(MAKE)
|
cd memkind && $(MAKE)
|
||||||
|
|
||||||
|
|
||||||
|
hdr_histogram: .make-prerequisites
|
||||||
|
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||||
|
cd hdr_histogram && $(MAKE)
|
||||||
|
|
||||||
|
.PHONY: hdr_histogram
|
||||||
|
|
||||||
ifeq ($(uname_S),SunOS)
|
ifeq ($(uname_S),SunOS)
|
||||||
# Make isinf() available
|
# Make isinf() available
|
||||||
LUA_CFLAGS= -D__C99FEATURES__=1
|
LUA_CFLAGS= -D__C99FEATURES__=1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' $(CFLAGS)
|
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' -DLUA_USE_MKSTEMP $(CFLAGS)
|
||||||
LUA_LDFLAGS+= $(LDFLAGS)
|
LUA_LDFLAGS+= $(LDFLAGS)
|
||||||
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
|
# 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
|
# challenging to cross-compile lua (and redis). These defines make it easier
|
||||||
# to fit redis into cross-compilation environments, which typically set AR.
|
# to fit redis into cross-compilation environments, which typically set AR.
|
||||||
AR=ar
|
AR=ar
|
||||||
ARFLAGS=rcu
|
ARFLAGS=rc
|
||||||
|
|
||||||
lua: .make-prerequisites
|
lua: .make-prerequisites
|
||||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||||
@ -86,7 +93,7 @@ JEMALLOC_LDFLAGS= $(LDFLAGS)
|
|||||||
|
|
||||||
jemalloc: .make-prerequisites
|
jemalloc: .make-prerequisites
|
||||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||||
cd jemalloc && ./configure --with-version=5.2.1-0-g0 --with-lg-quantum=3 --with-jemalloc-prefix=je_ --enable-cc-silence CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)"
|
cd jemalloc && ./configure --with-version=5.2.1-0-g0 --with-lg-quantum=3 --with-jemalloc-prefix=je_ CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)"
|
||||||
cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a
|
cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a
|
||||||
|
|
||||||
.PHONY: jemalloc
|
.PHONY: jemalloc
|
||||||
|
2
deps/README.md
vendored
2
deps/README.md
vendored
@ -17,7 +17,7 @@ active defragmentation logic. However this feature of Redis is not mandatory
|
|||||||
and Redis is able to understand if the Jemalloc version it is compiled
|
and Redis is able to understand if the Jemalloc version it is compiled
|
||||||
against supports such Redis-specific modifications. So in theory, if you
|
against supports such Redis-specific modifications. So in theory, if you
|
||||||
are not interested in the active defragmentation, you can replace Jemalloc
|
are not interested in the active defragmentation, you can replace Jemalloc
|
||||||
just following tose steps:
|
just following these steps:
|
||||||
|
|
||||||
1. Remove the jemalloc directory.
|
1. Remove the jemalloc directory.
|
||||||
2. Substitute it with the new jemalloc source tree.
|
2. Substitute it with the new jemalloc source tree.
|
||||||
|
121
deps/hdr_histogram/COPYING.txt
vendored
Normal file
121
deps/hdr_histogram/COPYING.txt
vendored
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
Creative 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.
|
41
deps/hdr_histogram/LICENSE.txt
vendored
Normal file
41
deps/hdr_histogram/LICENSE.txt
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
The 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.
|
20
deps/hdr_histogram/Makefile
vendored
Normal file
20
deps/hdr_histogram/Makefile
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
STD=
|
||||||
|
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)
|
||||||
|
|
||||||
|
hdr_histogram.o: hdr_histogram.h hdr_histogram.c
|
||||||
|
|
||||||
|
.c.o:
|
||||||
|
$(R_CC) -c $<
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.o
|
||||||
|
|
||||||
|
|
10
deps/hdr_histogram/README.md
vendored
Normal file
10
deps/hdr_histogram/README.md
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
HdrHistogram_c v0.11.0
|
||||||
|
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
This port contains a subset of the 'C' version of High Dynamic Range (HDR) Histogram available at [github.com/HdrHistogram/HdrHistogram_c](https://github.com/HdrHistogram/HdrHistogram_c).
|
||||||
|
|
||||||
|
|
||||||
|
The code present on `hdr_histogram.c`, `hdr_histogram.h`, and `hdr_atomic.c` 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/.
|
146
deps/hdr_histogram/hdr_atomic.h
vendored
Normal file
146
deps/hdr_histogram/hdr_atomic.h
vendored
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/**
|
||||||
|
* 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 <stdint.h>
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
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 <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
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__ */
|
1155
deps/hdr_histogram/hdr_histogram.c
vendored
Normal file
1155
deps/hdr_histogram/hdr_histogram.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
509
deps/hdr_histogram/hdr_histogram.h
vendored
Normal file
509
deps/hdr_histogram/hdr_histogram.h
vendored
Normal file
@ -0,0 +1,509 @@
|
|||||||
|
/**
|
||||||
|
* 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 <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
struct hdr_histogram
|
||||||
|
{
|
||||||
|
int64_t lowest_trackable_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_trackable_value The smallest possible value to be put into the
|
||||||
|
* histogram.
|
||||||
|
* @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_trackable_value is < 1 or the
|
||||||
|
* significant_figure value is outside of the allowed range, ENOMEM if malloc
|
||||||
|
* failed.
|
||||||
|
*/
|
||||||
|
int hdr_init(
|
||||||
|
int64_t lowest_trackable_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 contruction 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 occured 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 expexcted_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 contruction 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 occured 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 expexcted_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_trackable_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_trackable_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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(<i>value</i>) and {@literal <=} highestEquivalentValue(<i>value</i>)
|
||||||
|
*/
|
||||||
|
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_trackable_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_trackable_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 manuallying 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
|
1
deps/hiredis/.gitignore
vendored
1
deps/hiredis/.gitignore
vendored
@ -6,3 +6,4 @@
|
|||||||
/*.a
|
/*.a
|
||||||
/*.pc
|
/*.pc
|
||||||
*.dSYM
|
*.dSYM
|
||||||
|
tags
|
||||||
|
52
deps/hiredis/.travis.yml
vendored
52
deps/hiredis/.travis.yml
vendored
@ -1,5 +1,4 @@
|
|||||||
language: c
|
language: c
|
||||||
sudo: false
|
|
||||||
compiler:
|
compiler:
|
||||||
- gcc
|
- gcc
|
||||||
- clang
|
- clang
|
||||||
@ -8,17 +7,34 @@ os:
|
|||||||
- linux
|
- linux
|
||||||
- osx
|
- osx
|
||||||
|
|
||||||
|
dist: bionic
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- staging
|
- staging
|
||||||
- trying
|
- trying
|
||||||
- master
|
- master
|
||||||
|
- /^release\/.*$/
|
||||||
|
|
||||||
|
install:
|
||||||
|
- if [ "$BITS" == "64" ]; 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:
|
before_script:
|
||||||
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
|
- 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:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
|
sources:
|
||||||
|
- sourceline: 'ppa:chris-lea/redis-server'
|
||||||
packages:
|
packages:
|
||||||
- libc6-dbg
|
- libc6-dbg
|
||||||
- libc6-dev
|
- libc6-dev
|
||||||
@ -27,14 +43,20 @@ addons:
|
|||||||
- libc6-dbg:i386
|
- libc6-dbg:i386
|
||||||
- gcc-multilib
|
- gcc-multilib
|
||||||
- g++-multilib
|
- g++-multilib
|
||||||
|
- libssl-dev
|
||||||
|
- libssl-dev:i386
|
||||||
- valgrind
|
- valgrind
|
||||||
|
- redis
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- BITS="32"
|
- BITS="32"
|
||||||
- BITS="64"
|
- BITS="64"
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
|
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON";
|
||||||
|
if [ "$BITS" == "64" ]; then
|
||||||
|
EXTRA_CMAKE_OPTS="$EXTRA_CMAKE_OPTS -DENABLE_SSL_TESTS:BOOL=ON";
|
||||||
|
fi;
|
||||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
if [ "$BITS" == "32" ]; then
|
if [ "$BITS" == "32" ]; then
|
||||||
CFLAGS="-m32 -Werror";
|
CFLAGS="-m32 -Werror";
|
||||||
@ -58,12 +80,24 @@ script:
|
|||||||
fi;
|
fi;
|
||||||
fi;
|
fi;
|
||||||
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
|
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/
|
- mkdir build/ && cd build/
|
||||||
- cmake .. ${EXTRA_CMAKE_OPTS}
|
- cmake .. ${EXTRA_CMAKE_OPTS}
|
||||||
- make VERBOSE=1
|
- make VERBOSE=1
|
||||||
- ctest -V
|
- if [ "$BITS" == "64" ]; then
|
||||||
|
TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V;
|
||||||
|
else
|
||||||
|
SKIPS_AS_FAILS=1 ctest -V;
|
||||||
|
fi;
|
||||||
|
|
||||||
matrix:
|
jobs:
|
||||||
include:
|
include:
|
||||||
# Windows MinGW cross compile on Linux
|
# Windows MinGW cross compile on Linux
|
||||||
- os: linux
|
- os: linux
|
||||||
@ -89,9 +123,9 @@ matrix:
|
|||||||
- eval "${MATRIX_EVAL}"
|
- eval "${MATRIX_EVAL}"
|
||||||
install:
|
install:
|
||||||
- choco install ninja
|
- choco install ninja
|
||||||
|
- choco install -y memurai-developer
|
||||||
script:
|
script:
|
||||||
- mkdir build && cd build
|
- mkdir build && cd build
|
||||||
- cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 &&
|
- 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 &&
|
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v
|
||||||
ninja -v'
|
- ./hiredis-test.exe
|
||||||
- ctest -V
|
|
||||||
|
174
deps/hiredis/CHANGELOG.md
vendored
174
deps/hiredis/CHANGELOG.md
vendored
@ -1,18 +1,169 @@
|
|||||||
### 1.0.0 (unreleased)
|
## [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**:
|
**BREAKING CHANGES**:
|
||||||
|
|
||||||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
|
* `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))
|
||||||
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
|
* 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`.
|
||||||
|
|
||||||
User code should compare this to `size_t` values as well. If it was used to
|
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
|
||||||
compare to other values, casting might be necessary or can be removed, if
|
|
||||||
casting was applied before.
|
|
||||||
|
|
||||||
### 0.x.x (unreleased)
|
**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**:
|
**BREAKING CHANGES**:
|
||||||
|
|
||||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||||
@ -20,10 +171,6 @@
|
|||||||
User code should compare this to `size_t` values as well.
|
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.
|
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
|
||||||
|
|
||||||
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
|
|
||||||
|
|
||||||
### 0.14.0 (2018-09-25)
|
|
||||||
|
|
||||||
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
|
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
|
||||||
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
|
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
|
||||||
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
|
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
|
||||||
@ -196,4 +343,3 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
|
|||||||
### 0.10.0
|
### 0.10.0
|
||||||
|
|
||||||
* See commit log.
|
* See commit log.
|
||||||
|
|
||||||
|
91
deps/hiredis/CMakeLists.txt
vendored
91
deps/hiredis/CMakeLists.txt
vendored
@ -3,6 +3,8 @@ INCLUDE(GNUInstallDirs)
|
|||||||
PROJECT(hiredis)
|
PROJECT(hiredis)
|
||||||
|
|
||||||
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
|
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)
|
||||||
|
|
||||||
MACRO(getVersionBit name)
|
MACRO(getVersionBit name)
|
||||||
SET(VERSION_REGEX "^#define ${name} (.+)$")
|
SET(VERSION_REGEX "^#define ${name} (.+)$")
|
||||||
@ -22,7 +24,8 @@ PROJECT(hiredis VERSION "${VERSION}")
|
|||||||
|
|
||||||
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
|
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
|
||||||
|
|
||||||
ADD_LIBRARY(hiredis SHARED
|
SET(hiredis_sources
|
||||||
|
alloc.c
|
||||||
async.c
|
async.c
|
||||||
dict.c
|
dict.c
|
||||||
hiredis.c
|
hiredis.c
|
||||||
@ -31,20 +34,32 @@ ADD_LIBRARY(hiredis SHARED
|
|||||||
sds.c
|
sds.c
|
||||||
sockcompat.c)
|
sockcompat.c)
|
||||||
|
|
||||||
|
SET(hiredis_sources ${hiredis_sources})
|
||||||
|
|
||||||
|
IF(WIN32)
|
||||||
|
ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
ADD_LIBRARY(hiredis SHARED ${hiredis_sources})
|
||||||
|
|
||||||
SET_TARGET_PROPERTIES(hiredis
|
SET_TARGET_PROPERTIES(hiredis
|
||||||
PROPERTIES
|
PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE
|
||||||
VERSION "${HIREDIS_SONAME}")
|
VERSION "${HIREDIS_SONAME}")
|
||||||
IF(WIN32 OR MINGW)
|
IF(WIN32 OR MINGW)
|
||||||
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
|
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
|
||||||
ENDIF()
|
ENDIF()
|
||||||
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
|
|
||||||
|
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $<INSTALL_INTERFACE:.> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||||
|
|
||||||
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
|
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
|
||||||
|
|
||||||
INSTALL(TARGETS hiredis
|
INSTALL(TARGETS hiredis
|
||||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
EXPORT hiredis-targets
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|
||||||
INSTALL(FILES hiredis.h read.h sds.h async.h
|
INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h
|
||||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||||
|
|
||||||
INSTALL(DIRECTORY adapters
|
INSTALL(DIRECTORY adapters
|
||||||
@ -53,6 +68,26 @@ INSTALL(DIRECTORY adapters
|
|||||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
|
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
|
||||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||||
|
|
||||||
|
export(EXPORT hiredis-targets
|
||||||
|
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake"
|
||||||
|
NAMESPACE hiredis::)
|
||||||
|
|
||||||
|
SET(CMAKE_CONF_INSTALL_DIR share/hiredis)
|
||||||
|
SET(INCLUDE_INSTALL_DIR include)
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
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
|
||||||
|
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
|
||||||
|
|
||||||
|
|
||||||
IF(ENABLE_SSL)
|
IF(ENABLE_SSL)
|
||||||
IF (NOT OPENSSL_ROOT_DIR)
|
IF (NOT OPENSSL_ROOT_DIR)
|
||||||
IF (APPLE)
|
IF (APPLE)
|
||||||
@ -60,26 +95,66 @@ IF(ENABLE_SSL)
|
|||||||
ENDIF()
|
ENDIF()
|
||||||
ENDIF()
|
ENDIF()
|
||||||
FIND_PACKAGE(OpenSSL REQUIRED)
|
FIND_PACKAGE(OpenSSL REQUIRED)
|
||||||
ADD_LIBRARY(hiredis_ssl SHARED
|
SET(hiredis_ssl_sources
|
||||||
ssl.c)
|
ssl.c)
|
||||||
|
ADD_LIBRARY(hiredis_ssl SHARED
|
||||||
|
${hiredis_ssl_sources})
|
||||||
|
|
||||||
|
IF (APPLE)
|
||||||
|
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}")
|
||||||
|
|
||||||
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
|
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
|
||||||
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
|
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
|
||||||
|
IF (WIN32 OR MINGW)
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis)
|
||||||
|
ENDIF()
|
||||||
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
|
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
|
||||||
|
|
||||||
INSTALL(TARGETS hiredis_ssl
|
INSTALL(TARGETS hiredis_ssl
|
||||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
EXPORT hiredis_ssl-targets
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|
||||||
INSTALL(FILES hiredis_ssl.h
|
INSTALL(FILES hiredis_ssl.h
|
||||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||||
|
|
||||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
|
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
|
||||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||||
|
|
||||||
|
export(EXPORT hiredis_ssl-targets
|
||||||
|
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake"
|
||||||
|
NAMESPACE hiredis::)
|
||||||
|
|
||||||
|
SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl)
|
||||||
|
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()
|
ENDIF()
|
||||||
|
|
||||||
IF(NOT (WIN32 OR MINGW))
|
IF(NOT DISABLE_TESTS)
|
||||||
ENABLE_TESTING()
|
ENABLE_TESTING()
|
||||||
ADD_EXECUTABLE(hiredis-test test.c)
|
ADD_EXECUTABLE(hiredis-test test.c)
|
||||||
|
IF(ENABLE_SSL_TESTS)
|
||||||
|
ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1)
|
||||||
|
TARGET_LINK_LIBRARIES(hiredis-test hiredis hiredis_ssl)
|
||||||
|
ELSE()
|
||||||
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
|
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
|
||||||
|
ENDIF()
|
||||||
ADD_TEST(NAME hiredis-test
|
ADD_TEST(NAME hiredis-test
|
||||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
|
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
71
deps/hiredis/Makefile
vendored
71
deps/hiredis/Makefile
vendored
@ -3,16 +3,16 @@
|
|||||||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
# This file is released under the BSD license, see the COPYING file
|
# This file is released under the BSD license, see the COPYING file
|
||||||
|
|
||||||
OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o
|
OBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o
|
||||||
SSL_OBJ=ssl.o
|
SSL_OBJ=ssl.o
|
||||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
|
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push
|
||||||
ifeq ($(USE_SSL),1)
|
ifeq ($(USE_SSL),1)
|
||||||
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
|
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
|
||||||
endif
|
endif
|
||||||
TESTS=hiredis-test
|
TESTS=hiredis-test
|
||||||
LIBNAME=libhiredis
|
LIBNAME=libhiredis
|
||||||
SSL_LIBNAME=libhiredis_ssl
|
|
||||||
PKGCONFNAME=hiredis.pc
|
PKGCONFNAME=hiredis.pc
|
||||||
|
SSL_LIBNAME=libhiredis_ssl
|
||||||
SSL_PKGCONFNAME=hiredis_ssl.pc
|
SSL_PKGCONFNAME=hiredis_ssl.pc
|
||||||
|
|
||||||
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
|
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
|
||||||
@ -55,12 +55,17 @@ STLIBSUFFIX=a
|
|||||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
||||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||||
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
|
|
||||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
|
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
|
||||||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||||
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
|
|
||||||
STLIB_MAKE_CMD=$(AR) rcs
|
STLIB_MAKE_CMD=$(AR) rcs
|
||||||
|
|
||||||
|
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) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME)
|
||||||
|
|
||||||
# Platform-specific overrides
|
# Platform-specific overrides
|
||||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||||
|
|
||||||
@ -80,13 +85,22 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(uname_S),SunOS)
|
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
|
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
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
|
endif
|
||||||
ifeq ($(uname_S),Darwin)
|
ifeq ($(uname_S),Darwin)
|
||||||
DYLIBSUFFIX=dylib
|
DYLIBSUFFIX=dylib
|
||||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
||||||
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
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
|
endif
|
||||||
|
|
||||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
||||||
@ -95,15 +109,16 @@ all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# Deps (use make dep to generate this)
|
# Deps (use make dep to generate this)
|
||||||
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
|
alloc.o: alloc.c fmacros.h alloc.h
|
||||||
dict.o: dict.c fmacros.h dict.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
|
||||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
|
dict.o: dict.c fmacros.h alloc.h dict.h
|
||||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
|
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h
|
||||||
read.o: read.c fmacros.h read.h sds.h
|
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h
|
||||||
sds.o: sds.c sds.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
|
sockcompat.o: sockcompat.c sockcompat.h
|
||||||
ssl.o: ssl.c hiredis.h
|
ssl.o: ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h
|
||||||
test.o: test.c fmacros.h hiredis.h read.h sds.h
|
test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h
|
||||||
|
|
||||||
$(DYLIBNAME): $(OBJ)
|
$(DYLIBNAME): $(OBJ)
|
||||||
$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
|
$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
|
||||||
@ -112,7 +127,7 @@ $(STLIBNAME): $(OBJ)
|
|||||||
$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
|
$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
|
||||||
|
|
||||||
$(SSL_DYLIBNAME): $(SSL_OBJ)
|
$(SSL_DYLIBNAME): $(SSL_OBJ)
|
||||||
$(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
$(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS)
|
||||||
|
|
||||||
$(SSL_STLIBNAME): $(SSL_OBJ)
|
$(SSL_STLIBNAME): $(SSL_OBJ)
|
||||||
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
|
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
|
||||||
@ -146,6 +161,7 @@ hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
|
|||||||
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
|
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
|
||||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||||
|
|
||||||
|
|
||||||
ifndef AE_DIR
|
ifndef AE_DIR
|
||||||
hiredis-example-ae:
|
hiredis-example-ae:
|
||||||
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
||||||
@ -180,13 +196,19 @@ endif
|
|||||||
hiredis-example: examples/example.c $(STLIBNAME)
|
hiredis-example: examples/example.c $(STLIBNAME)
|
||||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
$(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)
|
examples: $(EXAMPLES)
|
||||||
|
|
||||||
TEST_LIBS = $(STLIBNAME)
|
TEST_LIBS = $(STLIBNAME)
|
||||||
ifeq ($(USE_SSL),1)
|
ifeq ($(USE_SSL),1)
|
||||||
TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread
|
TEST_LIBS += $(SSL_STLIBNAME)
|
||||||
|
TEST_LDFLAGS = $(SSL_LDFLAGS) -lssl -lcrypto -lpthread
|
||||||
endif
|
endif
|
||||||
|
|
||||||
hiredis-test: test.o $(TEST_LIBS)
|
hiredis-test: test.o $(TEST_LIBS)
|
||||||
|
$(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS)
|
||||||
|
|
||||||
hiredis-%: %.o $(STLIBNAME)
|
hiredis-%: %.o $(STLIBNAME)
|
||||||
$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
|
$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
|
||||||
@ -221,7 +243,7 @@ $(PKGCONFNAME): hiredis.h
|
|||||||
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
||||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||||
|
|
||||||
$(SSL_PKGCONFNAME): hiredis.h
|
$(SSL_PKGCONFNAME): hiredis_ssl.h
|
||||||
@echo "Generating $@ for pkgconfig..."
|
@echo "Generating $@ for pkgconfig..."
|
||||||
@echo prefix=$(PREFIX) > $@
|
@echo prefix=$(PREFIX) > $@
|
||||||
@echo exec_prefix=\$${prefix} >> $@
|
@echo exec_prefix=\$${prefix} >> $@
|
||||||
@ -237,7 +259,7 @@ $(SSL_PKGCONFNAME): hiredis.h
|
|||||||
|
|
||||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
||||||
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
|
$(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH)
|
||||||
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
|
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
|
||||||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
||||||
@ -245,6 +267,19 @@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
|||||||
mkdir -p $(INSTALL_PKGCONF_PATH)
|
mkdir -p $(INSTALL_PKGCONF_PATH)
|
||||||
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
|
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
|
||||||
|
|
||||||
|
ifeq ($(USE_SSL),1)
|
||||||
|
install: install-ssl
|
||||||
|
|
||||||
|
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)
|
||||||
|
$(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH)
|
||||||
|
mkdir -p $(INSTALL_PKGCONF_PATH)
|
||||||
|
$(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
|
||||||
|
endif
|
||||||
|
|
||||||
32bit:
|
32bit:
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
|
@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
|
||||||
|
272
deps/hiredis/README.md
vendored
272
deps/hiredis/README.md
vendored
@ -1,6 +1,6 @@
|
|||||||
[](https://travis-ci.org/redis/hiredis)
|
[](https://travis-ci.org/redis/hiredis)
|
||||||
|
|
||||||
**This Readme reflects the latest changed in the master branch. See [v0.13.3](https://github.com/redis/hiredis/tree/v0.13.3) for the Readme and documentation for the latest release.**
|
**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
|
||||||
|
|
||||||
@ -24,12 +24,31 @@ The library comes with multiple APIs. There is the
|
|||||||
|
|
||||||
## Upgrading to `1.0.0`
|
## Upgrading to `1.0.0`
|
||||||
|
|
||||||
Version 1.0.0 marks a stable release of hiredis.
|
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 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.
|
It also bundles the updated `sds` library, to sync up with upstream and Redis.
|
||||||
For most applications a recompile against the new hiredis should be enough.
|
|
||||||
For code changes see the [Changelog](CHANGELOG.md).
|
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`
|
## Upgrading from `<0.9.0`
|
||||||
|
|
||||||
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
|
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
|
||||||
@ -110,6 +129,8 @@ 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
|
`type` field in the `redisReply` should be used to test what kind of reply
|
||||||
was received:
|
was received:
|
||||||
|
|
||||||
|
### RESP2
|
||||||
|
|
||||||
* **`REDIS_REPLY_STATUS`**:
|
* **`REDIS_REPLY_STATUS`**:
|
||||||
* The command replied with a status reply. The status string can be accessed using `reply->str`.
|
* 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`.
|
The length of this string can be accessed using `reply->len`.
|
||||||
@ -134,16 +155,51 @@ was received:
|
|||||||
and can be accessed via `reply->element[..index..]`.
|
and can be accessed via `reply->element[..index..]`.
|
||||||
Redis may reply with nested arrays but this is fully supported.
|
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 equivelant 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` memeber, 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.
|
Replies should be freed using the `freeReplyObject()` function.
|
||||||
Note that this function will take care of freeing sub-reply objects
|
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
|
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).
|
free the sub replies (it is actually harmful and will corrupt the memory).
|
||||||
|
|
||||||
**Important:** the current version of hiredis (0.10.0) frees replies when the
|
**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
|
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
|
you use this API. The reply is cleaned up by hiredis _after_ the callback
|
||||||
returns. This behavior will probably change in future releases, so make sure to
|
returns. We may introduce a flag to make this configurable in future versions of the library.
|
||||||
keep an eye on the changelog when upgrading (see issue #39).
|
|
||||||
|
|
||||||
### Cleaning up
|
### Cleaning up
|
||||||
|
|
||||||
@ -205,16 +261,16 @@ a single call to `read(2)`):
|
|||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
redisAppendCommand(context,"SET foo bar");
|
redisAppendCommand(context,"SET foo bar");
|
||||||
redisAppendCommand(context,"GET foo");
|
redisAppendCommand(context,"GET foo");
|
||||||
redisGetReply(context,&reply); // reply for SET
|
redisGetReply(context,(void *)&reply); // reply for SET
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
redisGetReply(context,&reply); // reply for GET
|
redisGetReply(context,(void *)&reply); // reply for GET
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
```
|
```
|
||||||
This API can also be used to implement a blocking subscriber:
|
This API can also be used to implement a blocking subscriber:
|
||||||
```c
|
```c
|
||||||
reply = redisCommand(context,"SUBSCRIBE foo");
|
reply = redisCommand(context,"SUBSCRIBE foo");
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
while(redisGetReply(context,&reply) == REDIS_OK) {
|
while(redisGetReply(context,(void *)&reply) == REDIS_OK) {
|
||||||
// consume message
|
// consume message
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
}
|
}
|
||||||
@ -404,9 +460,199 @@ 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
|
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.
|
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 "hiredis.h"
|
||||||
|
#include "hiredis_ssl.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* An error variable to indicate what went wrong, if the context fails to
|
||||||
|
* initialize.
|
||||||
|
*/
|
||||||
|
redisSSLContextError ssl_error;
|
||||||
|
|
||||||
|
/* 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 = 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
|
||||||
|
) != REDIS_OK) {
|
||||||
|
printf("SSL error: %s\n", redisSSLContextGetError(ssl_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) != 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
|
## AUTHORS
|
||||||
|
|
||||||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
|
Salvatore Sanfilippo (antirez at gmail),\
|
||||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
Pieter Noordhuis (pcnoordhuis at gmail)\
|
||||||
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
|
Michael Grunder (michael dot grunder at gmail)
|
||||||
Jan-Erik Rediger (janerik at fnordig dot com)
|
|
||||||
|
_Hiredis is released under the BSD license._
|
||||||
|
7
deps/hiredis/adapters/ae.h
vendored
7
deps/hiredis/adapters/ae.h
vendored
@ -96,7 +96,7 @@ static void redisAeCleanup(void *privdata) {
|
|||||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||||
redisAeDelRead(privdata);
|
redisAeDelRead(privdata);
|
||||||
redisAeDelWrite(privdata);
|
redisAeDelWrite(privdata);
|
||||||
free(e);
|
hi_free(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
|
static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
|
||||||
@ -108,7 +108,10 @@ static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
/* Create container for context and r/w events */
|
/* Create container for context and r/w events */
|
||||||
e = (redisAeEvents*)malloc(sizeof(*e));
|
e = (redisAeEvents*)hi_malloc(sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
e->context = ac;
|
e->context = ac;
|
||||||
e->loop = loop;
|
e->loop = loop;
|
||||||
e->fd = c->fd;
|
e->fd = c->fd;
|
||||||
|
3
deps/hiredis/adapters/glib.h
vendored
3
deps/hiredis/adapters/glib.h
vendored
@ -134,6 +134,9 @@ redis_source_new (redisAsyncContext *ac)
|
|||||||
g_return_val_if_fail(ac != NULL, NULL);
|
g_return_val_if_fail(ac != NULL, NULL);
|
||||||
|
|
||||||
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
|
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
|
||||||
|
if (source == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
source->ac = ac;
|
source->ac = ac;
|
||||||
source->poll_fd.fd = c->fd;
|
source->poll_fd.fd = c->fd;
|
||||||
source->poll_fd.events = 0;
|
source->poll_fd.events = 0;
|
||||||
|
7
deps/hiredis/adapters/ivykis.h
vendored
7
deps/hiredis/adapters/ivykis.h
vendored
@ -43,7 +43,7 @@ static void redisIvykisCleanup(void *privdata) {
|
|||||||
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||||
|
|
||||||
iv_fd_unregister(&e->fd);
|
iv_fd_unregister(&e->fd);
|
||||||
free(e);
|
hi_free(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int redisIvykisAttach(redisAsyncContext *ac) {
|
static int redisIvykisAttach(redisAsyncContext *ac) {
|
||||||
@ -55,7 +55,10 @@ static int redisIvykisAttach(redisAsyncContext *ac) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
/* Create container for context and r/w events */
|
/* Create container for context and r/w events */
|
||||||
e = (redisIvykisEvents*)malloc(sizeof(*e));
|
e = (redisIvykisEvents*)hi_malloc(sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
e->context = ac;
|
e->context = ac;
|
||||||
|
|
||||||
/* Register functions to start/stop listening for events */
|
/* Register functions to start/stop listening for events */
|
||||||
|
38
deps/hiredis/adapters/libev.h
vendored
38
deps/hiredis/adapters/libev.h
vendored
@ -41,6 +41,7 @@ typedef struct redisLibevEvents {
|
|||||||
struct ev_loop *loop;
|
struct ev_loop *loop;
|
||||||
int reading, writing;
|
int reading, writing;
|
||||||
ev_io rev, wev;
|
ev_io rev, wev;
|
||||||
|
ev_timer timer;
|
||||||
} redisLibevEvents;
|
} redisLibevEvents;
|
||||||
|
|
||||||
static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
|
static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
|
||||||
@ -103,11 +104,39 @@ static void redisLibevDelWrite(void *privdata) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void redisLibevStopTimer(void *privdata) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
struct ev_loop *loop = e->loop;
|
||||||
|
((void)loop);
|
||||||
|
ev_timer_stop(EV_A_ &e->timer);
|
||||||
|
}
|
||||||
|
|
||||||
static void redisLibevCleanup(void *privdata) {
|
static void redisLibevCleanup(void *privdata) {
|
||||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
redisLibevDelRead(privdata);
|
redisLibevDelRead(privdata);
|
||||||
redisLibevDelWrite(privdata);
|
redisLibevDelWrite(privdata);
|
||||||
free(e);
|
redisLibevStopTimer(privdata);
|
||||||
|
hi_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) {
|
||||||
|
((void)revents);
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)timer->data;
|
||||||
|
redisAsyncHandleTimeout(e->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisLibevSetTimeout(void *privdata, struct timeval tv) {
|
||||||
|
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||||
|
struct ev_loop *loop = e->loop;
|
||||||
|
((void)loop);
|
||||||
|
|
||||||
|
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) {
|
static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
|
||||||
@ -119,14 +148,16 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
/* Create container for context and r/w events */
|
/* Create container for context and r/w events */
|
||||||
e = (redisLibevEvents*)malloc(sizeof(*e));
|
e = (redisLibevEvents*)hi_calloc(1, sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
e->context = ac;
|
e->context = ac;
|
||||||
#if EV_MULTIPLICITY
|
#if EV_MULTIPLICITY
|
||||||
e->loop = loop;
|
e->loop = loop;
|
||||||
#else
|
#else
|
||||||
e->loop = NULL;
|
e->loop = NULL;
|
||||||
#endif
|
#endif
|
||||||
e->reading = e->writing = 0;
|
|
||||||
e->rev.data = e;
|
e->rev.data = e;
|
||||||
e->wev.data = e;
|
e->wev.data = e;
|
||||||
|
|
||||||
@ -136,6 +167,7 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
|
|||||||
ac->ev.addWrite = redisLibevAddWrite;
|
ac->ev.addWrite = redisLibevAddWrite;
|
||||||
ac->ev.delWrite = redisLibevDelWrite;
|
ac->ev.delWrite = redisLibevDelWrite;
|
||||||
ac->ev.cleanup = redisLibevCleanup;
|
ac->ev.cleanup = redisLibevCleanup;
|
||||||
|
ac->ev.scheduleTimer = redisLibevSetTimeout;
|
||||||
ac->ev.data = e;
|
ac->ev.data = e;
|
||||||
|
|
||||||
/* Initialize read/write events */
|
/* Initialize read/write events */
|
||||||
|
7
deps/hiredis/adapters/libevent.h
vendored
7
deps/hiredis/adapters/libevent.h
vendored
@ -47,7 +47,7 @@ typedef struct redisLibeventEvents {
|
|||||||
} redisLibeventEvents;
|
} redisLibeventEvents;
|
||||||
|
|
||||||
static void redisLibeventDestroy(redisLibeventEvents *e) {
|
static void redisLibeventDestroy(redisLibeventEvents *e) {
|
||||||
free(e);
|
hi_free(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void redisLibeventHandler(int fd, short event, void *arg) {
|
static void redisLibeventHandler(int fd, short event, void *arg) {
|
||||||
@ -152,7 +152,10 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
/* Create container for context and r/w events */
|
/* Create container for context and r/w events */
|
||||||
e = (redisLibeventEvents*)calloc(1, sizeof(*e));
|
e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));
|
||||||
|
if (e == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
e->context = ac;
|
e->context = ac;
|
||||||
|
|
||||||
/* Register functions to start/stop listening for events */
|
/* Register functions to start/stop listening for events */
|
||||||
|
10
deps/hiredis/adapters/libuv.h
vendored
10
deps/hiredis/adapters/libuv.h
vendored
@ -73,7 +73,7 @@ static void redisLibuvDelWrite(void *privdata) {
|
|||||||
static void on_close(uv_handle_t* handle) {
|
static void on_close(uv_handle_t* handle) {
|
||||||
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||||
|
|
||||||
free(p);
|
hi_free(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -98,15 +98,13 @@ static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) {
|
|||||||
ac->ev.delWrite = redisLibuvDelWrite;
|
ac->ev.delWrite = redisLibuvDelWrite;
|
||||||
ac->ev.cleanup = redisLibuvCleanup;
|
ac->ev.cleanup = redisLibuvCleanup;
|
||||||
|
|
||||||
redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p));
|
redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p));
|
||||||
|
if (p == NULL)
|
||||||
if (!p) {
|
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
|
||||||
|
|
||||||
memset(p, 0, sizeof(*p));
|
memset(p, 0, sizeof(*p));
|
||||||
|
|
||||||
if (uv_poll_init(loop, &p->handle, c->fd) != 0) {
|
if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) {
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
deps/hiredis/adapters/macosx.h
vendored
7
deps/hiredis/adapters/macosx.h
vendored
@ -27,7 +27,7 @@ static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
|
|||||||
CFSocketInvalidate(redisRunLoop->socketRef);
|
CFSocketInvalidate(redisRunLoop->socketRef);
|
||||||
CFRelease(redisRunLoop->socketRef);
|
CFRelease(redisRunLoop->socketRef);
|
||||||
}
|
}
|
||||||
free(redisRunLoop);
|
hi_free(redisRunLoop);
|
||||||
}
|
}
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
@ -80,8 +80,9 @@ static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLo
|
|||||||
/* Nothing should be attached when something is already attached */
|
/* Nothing should be attached when something is already attached */
|
||||||
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
|
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
|
||||||
|
|
||||||
RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop));
|
RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop));
|
||||||
if( !redisRunLoop ) return REDIS_ERR;
|
if (redisRunLoop == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
/* Setup redis stuff */
|
/* Setup redis stuff */
|
||||||
redisRunLoop->context = redisAsyncCtx;
|
redisRunLoop->context = redisAsyncCtx;
|
||||||
|
86
deps/hiredis/alloc.c
vendored
Normal file
86
deps/hiredis/alloc.c
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot 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 "fmacros.h"
|
||||||
|
#include "alloc.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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
|
91
deps/hiredis/alloc.h
vendored
Normal file
91
deps/hiredis/alloc.h
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HIREDIS_ALLOC_H
|
||||||
|
#define HIREDIS_ALLOC_H
|
||||||
|
|
||||||
|
#include <stddef.h> /* for size_t */
|
||||||
|
|
||||||
|
#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) {
|
||||||
|
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 */
|
199
deps/hiredis/async.c
vendored
199
deps/hiredis/async.c
vendored
@ -30,6 +30,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
|
#include "alloc.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
@ -46,18 +47,24 @@
|
|||||||
|
|
||||||
#include "async_private.h"
|
#include "async_private.h"
|
||||||
|
|
||||||
/* Forward declaration of function in hiredis.c */
|
/* Forward declarations of hiredis.c functions */
|
||||||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
|
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. */
|
/* Functions managing dictionary of callbacks for pub/sub. */
|
||||||
static unsigned int callbackHash(const void *key) {
|
static unsigned int callbackHash(const void *key) {
|
||||||
return dictGenHashFunction((const unsigned char *)key,
|
return dictGenHashFunction((const unsigned char *)key,
|
||||||
sdslen((const sds)key));
|
hi_sdslen((const hisds)key));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *callbackValDup(void *privdata, const void *src) {
|
static void *callbackValDup(void *privdata, const void *src) {
|
||||||
((void) privdata);
|
((void) privdata);
|
||||||
redisCallback *dup = malloc(sizeof(*dup));
|
redisCallback *dup;
|
||||||
|
|
||||||
|
dup = hi_malloc(sizeof(*dup));
|
||||||
|
if (dup == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
memcpy(dup,src,sizeof(*dup));
|
memcpy(dup,src,sizeof(*dup));
|
||||||
return dup;
|
return dup;
|
||||||
}
|
}
|
||||||
@ -66,20 +73,20 @@ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2
|
|||||||
int l1, l2;
|
int l1, l2;
|
||||||
((void) privdata);
|
((void) privdata);
|
||||||
|
|
||||||
l1 = sdslen((const sds)key1);
|
l1 = hi_sdslen((const hisds)key1);
|
||||||
l2 = sdslen((const sds)key2);
|
l2 = hi_sdslen((const hisds)key2);
|
||||||
if (l1 != l2) return 0;
|
if (l1 != l2) return 0;
|
||||||
return memcmp(key1,key2,l1) == 0;
|
return memcmp(key1,key2,l1) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void callbackKeyDestructor(void *privdata, void *key) {
|
static void callbackKeyDestructor(void *privdata, void *key) {
|
||||||
((void) privdata);
|
((void) privdata);
|
||||||
sdsfree((sds)key);
|
hi_sdsfree((hisds)key);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void callbackValDestructor(void *privdata, void *val) {
|
static void callbackValDestructor(void *privdata, void *val) {
|
||||||
((void) privdata);
|
((void) privdata);
|
||||||
free(val);
|
hi_free(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static dictType callbackDict = {
|
static dictType callbackDict = {
|
||||||
@ -93,10 +100,19 @@ static dictType callbackDict = {
|
|||||||
|
|
||||||
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||||
redisAsyncContext *ac;
|
redisAsyncContext *ac;
|
||||||
|
dict *channels = NULL, *patterns = NULL;
|
||||||
|
|
||||||
ac = realloc(c,sizeof(redisAsyncContext));
|
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)
|
if (ac == NULL)
|
||||||
return NULL;
|
goto oom;
|
||||||
|
|
||||||
c = &(ac->c);
|
c = &(ac->c);
|
||||||
|
|
||||||
@ -108,6 +124,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
|||||||
ac->err = 0;
|
ac->err = 0;
|
||||||
ac->errstr = NULL;
|
ac->errstr = NULL;
|
||||||
ac->data = NULL;
|
ac->data = NULL;
|
||||||
|
ac->dataCleanup = NULL;
|
||||||
|
|
||||||
ac->ev.data = NULL;
|
ac->ev.data = NULL;
|
||||||
ac->ev.addRead = NULL;
|
ac->ev.addRead = NULL;
|
||||||
@ -124,9 +141,14 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
|||||||
ac->replies.tail = NULL;
|
ac->replies.tail = NULL;
|
||||||
ac->sub.invalid.head = NULL;
|
ac->sub.invalid.head = NULL;
|
||||||
ac->sub.invalid.tail = NULL;
|
ac->sub.invalid.tail = NULL;
|
||||||
ac->sub.channels = dictCreate(&callbackDict,NULL);
|
ac->sub.channels = channels;
|
||||||
ac->sub.patterns = dictCreate(&callbackDict,NULL);
|
ac->sub.patterns = patterns;
|
||||||
|
|
||||||
return ac;
|
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
|
/* We want the error field to be accessible directly instead of requiring
|
||||||
@ -145,16 +167,26 @@ redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
|
|||||||
redisContext *c;
|
redisContext *c;
|
||||||
redisAsyncContext *ac;
|
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;
|
myOptions.options |= REDIS_OPT_NONBLOCK;
|
||||||
c = redisConnectWithOptions(&myOptions);
|
c = redisConnectWithOptions(&myOptions);
|
||||||
if (c == NULL) {
|
if (c == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ac = redisAsyncInitialize(c);
|
ac = redisAsyncInitialize(c);
|
||||||
if (ac == NULL) {
|
if (ac == NULL) {
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set any configured async push handler */
|
||||||
|
redisAsyncSetPushCallback(ac, myOptions.async_push_cb);
|
||||||
|
|
||||||
__redisAsyncCopyError(ac);
|
__redisAsyncCopyError(ac);
|
||||||
return ac;
|
return ac;
|
||||||
}
|
}
|
||||||
@ -214,7 +246,7 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
|
|||||||
redisCallback *cb;
|
redisCallback *cb;
|
||||||
|
|
||||||
/* Copy callback from stack to heap */
|
/* Copy callback from stack to heap */
|
||||||
cb = malloc(sizeof(*cb));
|
cb = hi_malloc(sizeof(*cb));
|
||||||
if (cb == NULL)
|
if (cb == NULL)
|
||||||
return REDIS_ERR_OOM;
|
return REDIS_ERR_OOM;
|
||||||
|
|
||||||
@ -242,7 +274,7 @@ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target)
|
|||||||
/* Copy callback from heap to stack */
|
/* Copy callback from heap to stack */
|
||||||
if (target != NULL)
|
if (target != NULL)
|
||||||
memcpy(target,cb,sizeof(*cb));
|
memcpy(target,cb,sizeof(*cb));
|
||||||
free(cb);
|
hi_free(cb);
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -257,6 +289,14 @@ static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Helper function to free the context. */
|
/* Helper function to free the context. */
|
||||||
static void __redisAsyncFree(redisAsyncContext *ac) {
|
static void __redisAsyncFree(redisAsyncContext *ac) {
|
||||||
redisContext *c = &(ac->c);
|
redisContext *c = &(ac->c);
|
||||||
@ -272,18 +312,28 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
|
|||||||
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
|
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
|
||||||
__redisRunCallback(ac,&cb,NULL);
|
__redisRunCallback(ac,&cb,NULL);
|
||||||
|
|
||||||
/* Run subscription callbacks callbacks with NULL reply */
|
/* Run subscription callbacks with NULL reply */
|
||||||
|
if (ac->sub.channels) {
|
||||||
it = dictGetIterator(ac->sub.channels);
|
it = dictGetIterator(ac->sub.channels);
|
||||||
|
if (it != NULL) {
|
||||||
while ((de = dictNext(it)) != NULL)
|
while ((de = dictNext(it)) != NULL)
|
||||||
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
||||||
dictReleaseIterator(it);
|
dictReleaseIterator(it);
|
||||||
dictRelease(ac->sub.channels);
|
}
|
||||||
|
|
||||||
|
dictRelease(ac->sub.channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ac->sub.patterns) {
|
||||||
it = dictGetIterator(ac->sub.patterns);
|
it = dictGetIterator(ac->sub.patterns);
|
||||||
|
if (it != NULL) {
|
||||||
while ((de = dictNext(it)) != NULL)
|
while ((de = dictNext(it)) != NULL)
|
||||||
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
||||||
dictReleaseIterator(it);
|
dictReleaseIterator(it);
|
||||||
|
}
|
||||||
|
|
||||||
dictRelease(ac->sub.patterns);
|
dictRelease(ac->sub.patterns);
|
||||||
|
}
|
||||||
|
|
||||||
/* Signal event lib to clean up */
|
/* Signal event lib to clean up */
|
||||||
_EL_CLEANUP(ac);
|
_EL_CLEANUP(ac);
|
||||||
@ -298,6 +348,10 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ac->dataCleanup) {
|
||||||
|
ac->dataCleanup(ac->data);
|
||||||
|
}
|
||||||
|
|
||||||
/* Cleanup self */
|
/* Cleanup self */
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
}
|
}
|
||||||
@ -364,11 +418,11 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
|||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
int pvariant;
|
int pvariant;
|
||||||
char *stype;
|
char *stype;
|
||||||
sds sname;
|
hisds sname;
|
||||||
|
|
||||||
/* Custom reply functions are not supported for pub/sub. This will fail
|
/* Custom reply functions are not supported for pub/sub. This will fail
|
||||||
* very hard when they are used... */
|
* very hard when they are used... */
|
||||||
if (reply->type == REDIS_REPLY_ARRAY) {
|
if (reply->type == REDIS_REPLY_ARRAY || reply->type == REDIS_REPLY_PUSH) {
|
||||||
assert(reply->elements >= 2);
|
assert(reply->elements >= 2);
|
||||||
assert(reply->element[0]->type == REDIS_REPLY_STRING);
|
assert(reply->element[0]->type == REDIS_REPLY_STRING);
|
||||||
stype = reply->element[0]->str;
|
stype = reply->element[0]->str;
|
||||||
@ -381,7 +435,10 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
|||||||
|
|
||||||
/* Locate the right callback */
|
/* Locate the right callback */
|
||||||
assert(reply->element[1]->type == REDIS_REPLY_STRING);
|
assert(reply->element[1]->type == REDIS_REPLY_STRING);
|
||||||
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
|
sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len);
|
||||||
|
if (sname == NULL)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
de = dictFind(callbacks,sname);
|
de = dictFind(callbacks,sname);
|
||||||
if (de != NULL) {
|
if (de != NULL) {
|
||||||
cb = dictGetEntryVal(de);
|
cb = dictGetEntryVal(de);
|
||||||
@ -409,12 +466,39 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
|||||||
c->flags &= ~REDIS_SUBSCRIBED;
|
c->flags &= ~REDIS_SUBSCRIBED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sdsfree(sname);
|
hi_sdsfree(sname);
|
||||||
} else {
|
} else {
|
||||||
/* Shift callback for invalid commands. */
|
/* Shift callback for invalid commands. */
|
||||||
__redisShiftCallback(&ac->sub.invalid,dstcb);
|
__redisShiftCallback(&ac->sub.invalid,dstcb);
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
|
oom:
|
||||||
|
__redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
|
||||||
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void redisProcessCallbacks(redisAsyncContext *ac) {
|
void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||||
@ -427,7 +511,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
|||||||
if (reply == NULL) {
|
if (reply == NULL) {
|
||||||
/* When the connection is being disconnected and there are
|
/* When the connection is being disconnected and there are
|
||||||
* no more replies, this is the cue to really disconnect. */
|
* no more replies, this is the cue to really disconnect. */
|
||||||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0
|
if (c->flags & REDIS_DISCONNECTING && hi_sdslen(c->obuf) == 0
|
||||||
&& ac->replies.head == NULL) {
|
&& ac->replies.head == NULL) {
|
||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
return;
|
return;
|
||||||
@ -443,8 +527,18 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Even if the context is subscribed, pending regular callbacks will
|
/* Send any non-subscribe related PUSH messages to our PUSH handler
|
||||||
* get a reply before pub/sub messages arrive. */
|
* 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. */
|
||||||
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
|
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
|
||||||
/*
|
/*
|
||||||
* A spontaneous reply in a not-subscribed context can be the error
|
* A spontaneous reply in a not-subscribed context can be the error
|
||||||
@ -497,20 +591,31 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
|||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) {
|
||||||
|
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
}
|
||||||
|
|
||||||
/* Internal helper function to detect socket status the first time a read or
|
/* Internal helper function to detect socket status the first time a read or
|
||||||
* write event fires. When connecting was not successful, the connect callback
|
* write event fires. When connecting was not successful, the connect callback
|
||||||
* is called with a REDIS_ERR status and the context is free'd. */
|
* is called with a REDIS_ERR status and the context is free'd. */
|
||||||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
||||||
int completed = 0;
|
int completed = 0;
|
||||||
redisContext *c = &(ac->c);
|
redisContext *c = &(ac->c);
|
||||||
|
|
||||||
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
|
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
|
||||||
/* Error! */
|
/* Error! */
|
||||||
redisCheckSocketError(c);
|
redisCheckSocketError(c);
|
||||||
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
|
__redisAsyncHandleConnectFailure(ac);
|
||||||
__redisAsyncDisconnect(ac);
|
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
} else if (completed == 1) {
|
} else if (completed == 1) {
|
||||||
/* connected! */
|
/* connected! */
|
||||||
|
if (c->connection_type == REDIS_CONN_TCP &&
|
||||||
|
redisSetTcpNoDelay(c) == REDIS_ERR) {
|
||||||
|
__redisAsyncHandleConnectFailure(ac);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
|
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
|
||||||
c->flags |= REDIS_CONNECTED;
|
c->flags |= REDIS_CONNECTED;
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
@ -582,8 +687,6 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
|||||||
c->funcs->async_write(ac);
|
c->funcs->async_write(ac);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __redisSetError(redisContext *c, int type, const char *str);
|
|
||||||
|
|
||||||
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
|
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
|
||||||
redisContext *c = &(ac->c);
|
redisContext *c = &(ac->c);
|
||||||
redisCallback cb;
|
redisCallback cb;
|
||||||
@ -641,7 +744,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
|||||||
const char *cstr, *astr;
|
const char *cstr, *astr;
|
||||||
size_t clen, alen;
|
size_t clen, alen;
|
||||||
const char *p;
|
const char *p;
|
||||||
sds sname;
|
hisds sname;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Don't accept new commands when the connection is about to be closed. */
|
/* Don't accept new commands when the connection is about to be closed. */
|
||||||
@ -665,7 +768,10 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
|||||||
|
|
||||||
/* Add every channel/pattern to the list of subscription callbacks. */
|
/* Add every channel/pattern to the list of subscription callbacks. */
|
||||||
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
||||||
sname = sdsnewlen(astr,alen);
|
sname = hi_sdsnewlen(astr,alen);
|
||||||
|
if (sname == NULL)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
if (pvariant)
|
if (pvariant)
|
||||||
cbdict = ac->sub.patterns;
|
cbdict = ac->sub.patterns;
|
||||||
else
|
else
|
||||||
@ -680,7 +786,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
|||||||
|
|
||||||
ret = dictReplace(cbdict,sname,&cb);
|
ret = dictReplace(cbdict,sname,&cb);
|
||||||
|
|
||||||
if (ret == 0) sdsfree(sname);
|
if (ret == 0) hi_sdsfree(sname);
|
||||||
}
|
}
|
||||||
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
|
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
|
||||||
/* It is only useful to call (P)UNSUBSCRIBE when the context is
|
/* It is only useful to call (P)UNSUBSCRIBE when the context is
|
||||||
@ -709,6 +815,9 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
|||||||
_EL_ADD_WRITE(ac);
|
_EL_ADD_WRITE(ac);
|
||||||
|
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
|
oom:
|
||||||
|
__redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
|
||||||
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
|
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
|
||||||
@ -722,7 +831,7 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,14 +845,14 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata
|
|||||||
}
|
}
|
||||||
|
|
||||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
|
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
|
||||||
sds cmd;
|
hisds cmd;
|
||||||
int len;
|
int len;
|
||||||
int status;
|
int status;
|
||||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
||||||
if (len < 0)
|
if (len < 0)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||||
sdsfree(cmd);
|
hi_sdsfree(cmd);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -752,15 +861,27 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
|
redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) {
|
||||||
if (!ac->c.timeout) {
|
redisAsyncPushFn *old = ac->push_cb;
|
||||||
ac->c.timeout = calloc(1, sizeof(tv));
|
ac->push_cb = fn;
|
||||||
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tv.tv_sec == ac->c.timeout->tv_sec &&
|
int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
|
||||||
tv.tv_usec == ac->c.timeout->tv_usec) {
|
if (!ac->c.command_timeout) {
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*ac->c.timeout = tv;
|
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;
|
||||||
}
|
}
|
||||||
|
7
deps/hiredis/async.h
vendored
7
deps/hiredis/async.h
vendored
@ -70,6 +70,7 @@ typedef struct redisAsyncContext {
|
|||||||
|
|
||||||
/* Not used by hiredis */
|
/* Not used by hiredis */
|
||||||
void *data;
|
void *data;
|
||||||
|
void (*dataCleanup)(void *privdata);
|
||||||
|
|
||||||
/* Event library data and hooks */
|
/* Event library data and hooks */
|
||||||
struct {
|
struct {
|
||||||
@ -105,6 +106,9 @@ typedef struct redisAsyncContext {
|
|||||||
struct dict *channels;
|
struct dict *channels;
|
||||||
struct dict *patterns;
|
struct dict *patterns;
|
||||||
} sub;
|
} sub;
|
||||||
|
|
||||||
|
/* Any configured RESP3 PUSH handler */
|
||||||
|
redisAsyncPushFn *push_cb;
|
||||||
} redisAsyncContext;
|
} redisAsyncContext;
|
||||||
|
|
||||||
/* Functions that proxy to hiredis */
|
/* Functions that proxy to hiredis */
|
||||||
@ -117,7 +121,8 @@ redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
|||||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||||
|
|
||||||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
|
redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);
|
||||||
|
int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
|
||||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||||
void redisAsyncFree(redisAsyncContext *ac);
|
void redisAsyncFree(redisAsyncContext *ac);
|
||||||
|
|
||||||
|
23
deps/hiredis/async_private.h
vendored
23
deps/hiredis/async_private.h
vendored
@ -51,18 +51,21 @@
|
|||||||
#define _EL_CLEANUP(ctx) do { \
|
#define _EL_CLEANUP(ctx) do { \
|
||||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||||
ctx->ev.cleanup = NULL; \
|
ctx->ev.cleanup = NULL; \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
static inline void refreshTimeout(redisAsyncContext *ctx) {
|
static inline void refreshTimeout(redisAsyncContext *ctx) {
|
||||||
if (ctx->c.timeout && ctx->ev.scheduleTimer &&
|
#define REDIS_TIMER_ISSET(tvp) \
|
||||||
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) {
|
(tvp && ((tvp)->tv_sec || (tvp)->tv_usec))
|
||||||
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
|
|
||||||
// } else {
|
#define REDIS_EL_TIMER(ac, tvp) \
|
||||||
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
|
if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \
|
||||||
// if (ctx->c.timeout){
|
(ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \
|
||||||
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
|
}
|
||||||
// ctx->c.timeout->tv_usec);
|
|
||||||
// }
|
if (ctx->c.flags & REDIS_CONNECTED) {
|
||||||
|
REDIS_EL_TIMER(ctx, ctx->c.command_timeout);
|
||||||
|
} else {
|
||||||
|
REDIS_EL_TIMER(ctx, ctx->c.connect_timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
deps/hiredis/dict.c
vendored
36
deps/hiredis/dict.c
vendored
@ -34,6 +34,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
|
#include "alloc.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
@ -71,7 +72,10 @@ static void _dictReset(dict *ht) {
|
|||||||
|
|
||||||
/* Create a new hash table */
|
/* Create a new hash table */
|
||||||
static dict *dictCreate(dictType *type, void *privDataPtr) {
|
static dict *dictCreate(dictType *type, void *privDataPtr) {
|
||||||
dict *ht = malloc(sizeof(*ht));
|
dict *ht = hi_malloc(sizeof(*ht));
|
||||||
|
if (ht == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
_dictInit(ht,type,privDataPtr);
|
_dictInit(ht,type,privDataPtr);
|
||||||
return ht;
|
return ht;
|
||||||
}
|
}
|
||||||
@ -97,7 +101,9 @@ static int dictExpand(dict *ht, unsigned long size) {
|
|||||||
_dictInit(&n, ht->type, ht->privdata);
|
_dictInit(&n, ht->type, ht->privdata);
|
||||||
n.size = realsize;
|
n.size = realsize;
|
||||||
n.sizemask = realsize-1;
|
n.sizemask = realsize-1;
|
||||||
n.table = calloc(realsize,sizeof(dictEntry*));
|
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:
|
/* Copy all the elements from the old to the new table:
|
||||||
* note that if the old hash table is empty ht->size is zero,
|
* note that if the old hash table is empty ht->size is zero,
|
||||||
@ -124,7 +130,7 @@ static int dictExpand(dict *ht, unsigned long size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(ht->used == 0);
|
assert(ht->used == 0);
|
||||||
free(ht->table);
|
hi_free(ht->table);
|
||||||
|
|
||||||
/* Remap the new hashtable in the old */
|
/* Remap the new hashtable in the old */
|
||||||
*ht = n;
|
*ht = n;
|
||||||
@ -142,7 +148,10 @@ static int dictAdd(dict *ht, void *key, void *val) {
|
|||||||
return DICT_ERR;
|
return DICT_ERR;
|
||||||
|
|
||||||
/* Allocates the memory and stores key */
|
/* Allocates the memory and stores key */
|
||||||
entry = malloc(sizeof(*entry));
|
entry = hi_malloc(sizeof(*entry));
|
||||||
|
if (entry == NULL)
|
||||||
|
return DICT_ERR;
|
||||||
|
|
||||||
entry->next = ht->table[index];
|
entry->next = ht->table[index];
|
||||||
ht->table[index] = entry;
|
ht->table[index] = entry;
|
||||||
|
|
||||||
@ -166,6 +175,9 @@ static int dictReplace(dict *ht, void *key, void *val) {
|
|||||||
return 1;
|
return 1;
|
||||||
/* It already exists, get the entry */
|
/* It already exists, get the entry */
|
||||||
entry = dictFind(ht, key);
|
entry = dictFind(ht, key);
|
||||||
|
if (entry == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
/* Free the old value and set the new one */
|
/* Free the old value and set the new one */
|
||||||
/* Set the new value and free the old one. Note that it is important
|
/* 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
|
* to do that in this order, as the value may just be exactly the same
|
||||||
@ -199,7 +211,7 @@ static int dictDelete(dict *ht, const void *key) {
|
|||||||
|
|
||||||
dictFreeEntryKey(ht,de);
|
dictFreeEntryKey(ht,de);
|
||||||
dictFreeEntryVal(ht,de);
|
dictFreeEntryVal(ht,de);
|
||||||
free(de);
|
hi_free(de);
|
||||||
ht->used--;
|
ht->used--;
|
||||||
return DICT_OK;
|
return DICT_OK;
|
||||||
}
|
}
|
||||||
@ -222,13 +234,13 @@ static int _dictClear(dict *ht) {
|
|||||||
nextHe = he->next;
|
nextHe = he->next;
|
||||||
dictFreeEntryKey(ht, he);
|
dictFreeEntryKey(ht, he);
|
||||||
dictFreeEntryVal(ht, he);
|
dictFreeEntryVal(ht, he);
|
||||||
free(he);
|
hi_free(he);
|
||||||
ht->used--;
|
ht->used--;
|
||||||
he = nextHe;
|
he = nextHe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Free the table and the allocated cache structure */
|
/* Free the table and the allocated cache structure */
|
||||||
free(ht->table);
|
hi_free(ht->table);
|
||||||
/* Re-initialize the table */
|
/* Re-initialize the table */
|
||||||
_dictReset(ht);
|
_dictReset(ht);
|
||||||
return DICT_OK; /* never fails */
|
return DICT_OK; /* never fails */
|
||||||
@ -237,7 +249,7 @@ static int _dictClear(dict *ht) {
|
|||||||
/* Clear & Release the hash table */
|
/* Clear & Release the hash table */
|
||||||
static void dictRelease(dict *ht) {
|
static void dictRelease(dict *ht) {
|
||||||
_dictClear(ht);
|
_dictClear(ht);
|
||||||
free(ht);
|
hi_free(ht);
|
||||||
}
|
}
|
||||||
|
|
||||||
static dictEntry *dictFind(dict *ht, const void *key) {
|
static dictEntry *dictFind(dict *ht, const void *key) {
|
||||||
@ -256,7 +268,9 @@ static dictEntry *dictFind(dict *ht, const void *key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static dictIterator *dictGetIterator(dict *ht) {
|
static dictIterator *dictGetIterator(dict *ht) {
|
||||||
dictIterator *iter = malloc(sizeof(*iter));
|
dictIterator *iter = hi_malloc(sizeof(*iter));
|
||||||
|
if (iter == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
iter->ht = ht;
|
iter->ht = ht;
|
||||||
iter->index = -1;
|
iter->index = -1;
|
||||||
@ -286,7 +300,7 @@ static dictEntry *dictNext(dictIterator *iter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void dictReleaseIterator(dictIterator *iter) {
|
static void dictReleaseIterator(dictIterator *iter) {
|
||||||
free(iter);
|
hi_free(iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------- private functions ------------------------------ */
|
/* ------------------------- private functions ------------------------------ */
|
||||||
@ -294,7 +308,7 @@ static void dictReleaseIterator(dictIterator *iter) {
|
|||||||
/* Expand the hash table if needed */
|
/* Expand the hash table if needed */
|
||||||
static int _dictExpandIfNeeded(dict *ht) {
|
static int _dictExpandIfNeeded(dict *ht) {
|
||||||
/* If the hash table is empty expand it to the initial size,
|
/* If the hash table is empty expand it to the initial size,
|
||||||
* if the table is "full" dobule its size. */
|
* if the table is "full" double its size. */
|
||||||
if (ht->size == 0)
|
if (ht->size == 0)
|
||||||
return dictExpand(ht, DICT_HT_INITIAL_SIZE);
|
return dictExpand(ht, DICT_HT_INITIAL_SIZE);
|
||||||
if (ht->used == ht->size)
|
if (ht->used == ht->size)
|
||||||
|
3
deps/hiredis/examples/CMakeLists.txt
vendored
3
deps/hiredis/examples/CMakeLists.txt
vendored
@ -44,3 +44,6 @@ ENDIF()
|
|||||||
|
|
||||||
ADD_EXECUTABLE(example example.c)
|
ADD_EXECUTABLE(example example.c)
|
||||||
TARGET_LINK_LIBRARIES(example hiredis)
|
TARGET_LINK_LIBRARIES(example hiredis)
|
||||||
|
|
||||||
|
ADD_EXECUTABLE(example-push example-push.c)
|
||||||
|
TARGET_LINK_LIBRARIES(example-push hiredis)
|
||||||
|
2
deps/hiredis/examples/example-ivykis.c
vendored
2
deps/hiredis/examples/example-ivykis.c
vendored
@ -33,7 +33,9 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main (int argc, char **argv) {
|
int main (int argc, char **argv) {
|
||||||
|
#ifndef _WIN32
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#endif
|
||||||
|
|
||||||
iv_init();
|
iv_init();
|
||||||
|
|
||||||
|
2
deps/hiredis/examples/example-libev.c
vendored
2
deps/hiredis/examples/example-libev.c
vendored
@ -33,7 +33,9 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main (int argc, char **argv) {
|
int main (int argc, char **argv) {
|
||||||
|
#ifndef _WIN32
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#endif
|
||||||
|
|
||||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||||
if (c->err) {
|
if (c->err) {
|
||||||
|
19
deps/hiredis/examples/example-libevent-ssl.c
vendored
19
deps/hiredis/examples/example-libevent-ssl.c
vendored
@ -34,7 +34,10 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main (int argc, char **argv) {
|
int main (int argc, char **argv) {
|
||||||
|
#ifndef _WIN32
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#endif
|
||||||
|
|
||||||
struct event_base *base = event_base_new();
|
struct event_base *base = event_base_new();
|
||||||
if (argc < 5) {
|
if (argc < 5) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@ -52,13 +55,25 @@ int main (int argc, char **argv) {
|
|||||||
const char *certKey = argv[5];
|
const char *certKey = argv[5];
|
||||||
const char *caCert = argc > 5 ? argv[6] : NULL;
|
const char *caCert = argc > 5 ? argv[6] : NULL;
|
||||||
|
|
||||||
|
redisSSLContext *ssl;
|
||||||
|
redisSSLContextError ssl_error;
|
||||||
|
|
||||||
|
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);
|
redisAsyncContext *c = redisAsyncConnect(hostname, port);
|
||||||
if (c->err) {
|
if (c->err) {
|
||||||
/* Let *c leak for now... */
|
/* Let *c leak for now... */
|
||||||
printf("Error: %s\n", c->errstr);
|
printf("Error: %s\n", c->errstr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
|
if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK) {
|
||||||
printf("SSL Error!\n");
|
printf("SSL Error!\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -69,5 +84,7 @@ int main (int argc, char **argv) {
|
|||||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
|
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
|
||||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||||
event_base_dispatch(base);
|
event_base_dispatch(base);
|
||||||
|
|
||||||
|
redisFreeSSLContext(ssl);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
5
deps/hiredis/examples/example-libevent.c
vendored
5
deps/hiredis/examples/example-libevent.c
vendored
@ -38,13 +38,16 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main (int argc, char **argv) {
|
int main (int argc, char **argv) {
|
||||||
|
#ifndef _WIN32
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#endif
|
||||||
|
|
||||||
struct event_base *base = event_base_new();
|
struct event_base *base = event_base_new();
|
||||||
redisOptions options = {0};
|
redisOptions options = {0};
|
||||||
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
|
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
|
||||||
struct timeval tv = {0};
|
struct timeval tv = {0};
|
||||||
tv.tv_sec = 1;
|
tv.tv_sec = 1;
|
||||||
options.timeout = &tv;
|
options.connect_timeout = &tv;
|
||||||
|
|
||||||
|
|
||||||
redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
|
redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
|
||||||
|
3
deps/hiredis/examples/example-libuv.c
vendored
3
deps/hiredis/examples/example-libuv.c
vendored
@ -33,7 +33,10 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main (int argc, char **argv) {
|
int main (int argc, char **argv) {
|
||||||
|
#ifndef _WIN32
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#endif
|
||||||
|
|
||||||
uv_loop_t* loop = uv_default_loop();
|
uv_loop_t* loop = uv_default_loop();
|
||||||
|
|
||||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||||
|
160
deps/hiredis/examples/example-push.c
vendored
Normal file
160
deps/hiredis/examples/example-push.c
vendored
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <hiredis.h>
|
||||||
|
#include <win32.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
17
deps/hiredis/examples/example-ssl.c
vendored
17
deps/hiredis/examples/example-ssl.c
vendored
@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
#include <hiredis.h>
|
#include <hiredis.h>
|
||||||
#include <hiredis_ssl.h>
|
#include <hiredis_ssl.h>
|
||||||
|
#include <win32.h>
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
unsigned int j;
|
unsigned int j;
|
||||||
|
redisSSLContext *ssl;
|
||||||
|
redisSSLContextError ssl_error;
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
if (argc < 4) {
|
if (argc < 4) {
|
||||||
@ -19,10 +22,18 @@ int main(int argc, char **argv) {
|
|||||||
const char *key = argv[4];
|
const char *key = argv[4];
|
||||||
const char *ca = argc > 4 ? argv[5] : NULL;
|
const char *ca = argc > 4 ? argv[5] : NULL;
|
||||||
|
|
||||||
|
redisInitOpenSSL();
|
||||||
|
ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
|
||||||
|
if (!ssl) {
|
||||||
|
printf("SSL Context error: %s\n",
|
||||||
|
redisSSLContextGetError(ssl_error));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
struct timeval tv = { 1, 500000 }; // 1.5 seconds
|
struct timeval tv = { 1, 500000 }; // 1.5 seconds
|
||||||
redisOptions options = {0};
|
redisOptions options = {0};
|
||||||
REDIS_OPTIONS_SET_TCP(&options, hostname, port);
|
REDIS_OPTIONS_SET_TCP(&options, hostname, port);
|
||||||
options.timeout = &tv;
|
options.connect_timeout = &tv;
|
||||||
c = redisConnectWithOptions(&options);
|
c = redisConnectWithOptions(&options);
|
||||||
|
|
||||||
if (c == NULL || c->err) {
|
if (c == NULL || c->err) {
|
||||||
@ -35,7 +46,7 @@ int main(int argc, char **argv) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) {
|
if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
|
||||||
printf("Couldn't initialize SSL!\n");
|
printf("Couldn't initialize SSL!\n");
|
||||||
printf("Error: %s\n", c->errstr);
|
printf("Error: %s\n", c->errstr);
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
@ -93,5 +104,7 @@ int main(int argc, char **argv) {
|
|||||||
/* Disconnects and frees the context */
|
/* Disconnects and frees the context */
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
|
|
||||||
|
redisFreeSSLContext(ssl);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
2
deps/hiredis/examples/example.c
vendored
2
deps/hiredis/examples/example.c
vendored
@ -1,8 +1,8 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <hiredis.h>
|
#include <hiredis.h>
|
||||||
|
#include <win32.h>
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
unsigned int j, isunix = 0;
|
unsigned int j, isunix = 0;
|
||||||
|
13
deps/hiredis/hiredis-config.cmake.in
vendored
Normal file
13
deps/hiredis/hiredis-config.cmake.in
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
|
||||||
|
|
||||||
|
IF (NOT TARGET hiredis::hiredis)
|
||||||
|
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
SET(hiredis_LIBRARIES hiredis::hiredis)
|
||||||
|
SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR})
|
||||||
|
|
||||||
|
check_required_components(hiredis)
|
||||||
|
|
308
deps/hiredis/hiredis.c
vendored
308
deps/hiredis/hiredis.c
vendored
@ -44,8 +44,11 @@
|
|||||||
#include "async.h"
|
#include "async.h"
|
||||||
#include "win32.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 = {
|
static redisContextFuncs redisContextDefaultFuncs = {
|
||||||
.free_privdata = NULL,
|
.free_privctx = NULL,
|
||||||
.async_read = redisAsyncRead,
|
.async_read = redisAsyncRead,
|
||||||
.async_write = redisAsyncWrite,
|
.async_write = redisAsyncWrite,
|
||||||
.read = redisNetRead,
|
.read = redisNetRead,
|
||||||
@ -74,7 +77,7 @@ static redisReplyObjectFunctions defaultFunctions = {
|
|||||||
|
|
||||||
/* Create a reply object */
|
/* Create a reply object */
|
||||||
static redisReply *createReplyObject(int type) {
|
static redisReply *createReplyObject(int type) {
|
||||||
redisReply *r = calloc(1,sizeof(*r));
|
redisReply *r = hi_calloc(1,sizeof(*r));
|
||||||
|
|
||||||
if (r == NULL)
|
if (r == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -97,20 +100,22 @@ void freeReplyObject(void *reply) {
|
|||||||
case REDIS_REPLY_ARRAY:
|
case REDIS_REPLY_ARRAY:
|
||||||
case REDIS_REPLY_MAP:
|
case REDIS_REPLY_MAP:
|
||||||
case REDIS_REPLY_SET:
|
case REDIS_REPLY_SET:
|
||||||
|
case REDIS_REPLY_PUSH:
|
||||||
if (r->element != NULL) {
|
if (r->element != NULL) {
|
||||||
for (j = 0; j < r->elements; j++)
|
for (j = 0; j < r->elements; j++)
|
||||||
freeReplyObject(r->element[j]);
|
freeReplyObject(r->element[j]);
|
||||||
free(r->element);
|
hi_free(r->element);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_ERROR:
|
case REDIS_REPLY_ERROR:
|
||||||
case REDIS_REPLY_STATUS:
|
case REDIS_REPLY_STATUS:
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
case REDIS_REPLY_DOUBLE:
|
case REDIS_REPLY_DOUBLE:
|
||||||
free(r->str);
|
case REDIS_REPLY_VERB:
|
||||||
|
hi_free(r->str);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
free(r);
|
hi_free(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
|
static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
|
||||||
@ -128,22 +133,18 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
|||||||
|
|
||||||
/* Copy string value */
|
/* Copy string value */
|
||||||
if (task->type == REDIS_REPLY_VERB) {
|
if (task->type == REDIS_REPLY_VERB) {
|
||||||
buf = malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
|
buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
|
||||||
if (buf == NULL) {
|
if (buf == NULL) goto oom;
|
||||||
freeReplyObject(r);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
memcpy(r->vtype,str,3);
|
memcpy(r->vtype,str,3);
|
||||||
r->vtype[3] = '\0';
|
r->vtype[3] = '\0';
|
||||||
memcpy(buf,str+4,len-4);
|
memcpy(buf,str+4,len-4);
|
||||||
buf[len-4] = '\0';
|
buf[len-4] = '\0';
|
||||||
r->len = len - 4;
|
r->len = len - 4;
|
||||||
} else {
|
} else {
|
||||||
buf = malloc(len+1);
|
buf = hi_malloc(len+1);
|
||||||
if (buf == NULL) {
|
if (buf == NULL) goto oom;
|
||||||
freeReplyObject(r);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
memcpy(buf,str,len);
|
memcpy(buf,str,len);
|
||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
r->len = len;
|
r->len = len;
|
||||||
@ -154,10 +155,15 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
|||||||
parent = task->parent->obj;
|
parent = task->parent->obj;
|
||||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||||
parent->type == REDIS_REPLY_MAP ||
|
parent->type == REDIS_REPLY_MAP ||
|
||||||
parent->type == REDIS_REPLY_SET);
|
parent->type == REDIS_REPLY_SET ||
|
||||||
|
parent->type == REDIS_REPLY_PUSH);
|
||||||
parent->element[task->idx] = r;
|
parent->element[task->idx] = r;
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
oom:
|
||||||
|
freeReplyObject(r);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
||||||
@ -168,7 +174,7 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (elements > 0) {
|
if (elements > 0) {
|
||||||
r->element = calloc(elements,sizeof(redisReply*));
|
r->element = hi_calloc(elements,sizeof(redisReply*));
|
||||||
if (r->element == NULL) {
|
if (r->element == NULL) {
|
||||||
freeReplyObject(r);
|
freeReplyObject(r);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -181,7 +187,8 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
|||||||
parent = task->parent->obj;
|
parent = task->parent->obj;
|
||||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||||
parent->type == REDIS_REPLY_MAP ||
|
parent->type == REDIS_REPLY_MAP ||
|
||||||
parent->type == REDIS_REPLY_SET);
|
parent->type == REDIS_REPLY_SET ||
|
||||||
|
parent->type == REDIS_REPLY_PUSH);
|
||||||
parent->element[task->idx] = r;
|
parent->element[task->idx] = r;
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
@ -200,7 +207,8 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
|
|||||||
parent = task->parent->obj;
|
parent = task->parent->obj;
|
||||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||||
parent->type == REDIS_REPLY_MAP ||
|
parent->type == REDIS_REPLY_MAP ||
|
||||||
parent->type == REDIS_REPLY_SET);
|
parent->type == REDIS_REPLY_SET ||
|
||||||
|
parent->type == REDIS_REPLY_PUSH);
|
||||||
parent->element[task->idx] = r;
|
parent->element[task->idx] = r;
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
@ -214,7 +222,7 @@ static void *createDoubleObject(const redisReadTask *task, double value, char *s
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
r->dval = value;
|
r->dval = value;
|
||||||
r->str = malloc(len+1);
|
r->str = hi_malloc(len+1);
|
||||||
if (r->str == NULL) {
|
if (r->str == NULL) {
|
||||||
freeReplyObject(r);
|
freeReplyObject(r);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -249,7 +257,8 @@ static void *createNilObject(const redisReadTask *task) {
|
|||||||
parent = task->parent->obj;
|
parent = task->parent->obj;
|
||||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||||
parent->type == REDIS_REPLY_MAP ||
|
parent->type == REDIS_REPLY_MAP ||
|
||||||
parent->type == REDIS_REPLY_SET);
|
parent->type == REDIS_REPLY_SET ||
|
||||||
|
parent->type == REDIS_REPLY_PUSH);
|
||||||
parent->element[task->idx] = r;
|
parent->element[task->idx] = r;
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
@ -297,7 +306,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
|||||||
const char *c = format;
|
const char *c = format;
|
||||||
char *cmd = NULL; /* final command */
|
char *cmd = NULL; /* final command */
|
||||||
int pos; /* position in final command */
|
int pos; /* position in final command */
|
||||||
sds curarg, newarg; /* current argument */
|
hisds curarg, newarg; /* current argument */
|
||||||
int touched = 0; /* was the current argument touched? */
|
int touched = 0; /* was the current argument touched? */
|
||||||
char **curargv = NULL, **newargv = NULL;
|
char **curargv = NULL, **newargv = NULL;
|
||||||
int argc = 0;
|
int argc = 0;
|
||||||
@ -310,7 +319,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Build the command string accordingly to protocol */
|
/* Build the command string accordingly to protocol */
|
||||||
curarg = sdsempty();
|
curarg = hi_sdsempty();
|
||||||
if (curarg == NULL)
|
if (curarg == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -318,19 +327,19 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
|||||||
if (*c != '%' || c[1] == '\0') {
|
if (*c != '%' || c[1] == '\0') {
|
||||||
if (*c == ' ') {
|
if (*c == ' ') {
|
||||||
if (touched) {
|
if (touched) {
|
||||||
newargv = realloc(curargv,sizeof(char*)*(argc+1));
|
newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
|
||||||
if (newargv == NULL) goto memory_err;
|
if (newargv == NULL) goto memory_err;
|
||||||
curargv = newargv;
|
curargv = newargv;
|
||||||
curargv[argc++] = curarg;
|
curargv[argc++] = curarg;
|
||||||
totlen += bulklen(sdslen(curarg));
|
totlen += bulklen(hi_sdslen(curarg));
|
||||||
|
|
||||||
/* curarg is put in argv so it can be overwritten. */
|
/* curarg is put in argv so it can be overwritten. */
|
||||||
curarg = sdsempty();
|
curarg = hi_sdsempty();
|
||||||
if (curarg == NULL) goto memory_err;
|
if (curarg == NULL) goto memory_err;
|
||||||
touched = 0;
|
touched = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newarg = sdscatlen(curarg,c,1);
|
newarg = hi_sdscatlen(curarg,c,1);
|
||||||
if (newarg == NULL) goto memory_err;
|
if (newarg == NULL) goto memory_err;
|
||||||
curarg = newarg;
|
curarg = newarg;
|
||||||
touched = 1;
|
touched = 1;
|
||||||
@ -347,16 +356,16 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
|||||||
arg = va_arg(ap,char*);
|
arg = va_arg(ap,char*);
|
||||||
size = strlen(arg);
|
size = strlen(arg);
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
newarg = sdscatlen(curarg,arg,size);
|
newarg = hi_sdscatlen(curarg,arg,size);
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
arg = va_arg(ap,char*);
|
arg = va_arg(ap,char*);
|
||||||
size = va_arg(ap,size_t);
|
size = va_arg(ap,size_t);
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
newarg = sdscatlen(curarg,arg,size);
|
newarg = hi_sdscatlen(curarg,arg,size);
|
||||||
break;
|
break;
|
||||||
case '%':
|
case '%':
|
||||||
newarg = sdscat(curarg,"%");
|
newarg = hi_sdscat(curarg,"%");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* Try to detect printf format */
|
/* Try to detect printf format */
|
||||||
@ -444,7 +453,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
|||||||
if (_l < sizeof(_format)-2) {
|
if (_l < sizeof(_format)-2) {
|
||||||
memcpy(_format,c,_l);
|
memcpy(_format,c,_l);
|
||||||
_format[_l] = '\0';
|
_format[_l] = '\0';
|
||||||
newarg = sdscatvprintf(curarg,_format,_cpy);
|
newarg = hi_sdscatvprintf(curarg,_format,_cpy);
|
||||||
|
|
||||||
/* Update current position (note: outer blocks
|
/* Update current position (note: outer blocks
|
||||||
* increment c twice so compensate here) */
|
* increment c twice so compensate here) */
|
||||||
@ -467,13 +476,13 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
|||||||
|
|
||||||
/* Add the last argument if needed */
|
/* Add the last argument if needed */
|
||||||
if (touched) {
|
if (touched) {
|
||||||
newargv = realloc(curargv,sizeof(char*)*(argc+1));
|
newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
|
||||||
if (newargv == NULL) goto memory_err;
|
if (newargv == NULL) goto memory_err;
|
||||||
curargv = newargv;
|
curargv = newargv;
|
||||||
curargv[argc++] = curarg;
|
curargv[argc++] = curarg;
|
||||||
totlen += bulklen(sdslen(curarg));
|
totlen += bulklen(hi_sdslen(curarg));
|
||||||
} else {
|
} else {
|
||||||
sdsfree(curarg);
|
hi_sdsfree(curarg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear curarg because it was put in curargv or was free'd. */
|
/* Clear curarg because it was put in curargv or was free'd. */
|
||||||
@ -483,22 +492,22 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
|||||||
totlen += 1+countDigits(argc)+2;
|
totlen += 1+countDigits(argc)+2;
|
||||||
|
|
||||||
/* Build the command at protocol level */
|
/* Build the command at protocol level */
|
||||||
cmd = malloc(totlen+1);
|
cmd = hi_malloc(totlen+1);
|
||||||
if (cmd == NULL) goto memory_err;
|
if (cmd == NULL) goto memory_err;
|
||||||
|
|
||||||
pos = sprintf(cmd,"*%d\r\n",argc);
|
pos = sprintf(cmd,"*%d\r\n",argc);
|
||||||
for (j = 0; j < argc; j++) {
|
for (j = 0; j < argc; j++) {
|
||||||
pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j]));
|
pos += sprintf(cmd+pos,"$%zu\r\n",hi_sdslen(curargv[j]));
|
||||||
memcpy(cmd+pos,curargv[j],sdslen(curargv[j]));
|
memcpy(cmd+pos,curargv[j],hi_sdslen(curargv[j]));
|
||||||
pos += sdslen(curargv[j]);
|
pos += hi_sdslen(curargv[j]);
|
||||||
sdsfree(curargv[j]);
|
hi_sdsfree(curargv[j]);
|
||||||
cmd[pos++] = '\r';
|
cmd[pos++] = '\r';
|
||||||
cmd[pos++] = '\n';
|
cmd[pos++] = '\n';
|
||||||
}
|
}
|
||||||
assert(pos == totlen);
|
assert(pos == totlen);
|
||||||
cmd[pos] = '\0';
|
cmd[pos] = '\0';
|
||||||
|
|
||||||
free(curargv);
|
hi_free(curargv);
|
||||||
*target = cmd;
|
*target = cmd;
|
||||||
return totlen;
|
return totlen;
|
||||||
|
|
||||||
@ -513,12 +522,12 @@ memory_err:
|
|||||||
cleanup:
|
cleanup:
|
||||||
if (curargv) {
|
if (curargv) {
|
||||||
while(argc--)
|
while(argc--)
|
||||||
sdsfree(curargv[argc]);
|
hi_sdsfree(curargv[argc]);
|
||||||
free(curargv);
|
hi_free(curargv);
|
||||||
}
|
}
|
||||||
|
|
||||||
sdsfree(curarg);
|
hi_sdsfree(curarg);
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
return error_type;
|
return error_type;
|
||||||
}
|
}
|
||||||
@ -550,16 +559,16 @@ int redisFormatCommand(char **target, const char *format, ...) {
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Format a command according to the Redis protocol using an sds string and
|
/* Format a command according to the Redis protocol using an hisds string and
|
||||||
* sdscatfmt for the processing of arguments. This function takes the
|
* hi_sdscatfmt for the processing of arguments. This function takes the
|
||||||
* number of arguments, an array with arguments and an array with their
|
* 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
|
* lengths. If the latter is set to NULL, strlen will be used to compute the
|
||||||
* argument lengths.
|
* argument lengths.
|
||||||
*/
|
*/
|
||||||
int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
|
int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv,
|
||||||
const size_t *argvlen)
|
const size_t *argvlen)
|
||||||
{
|
{
|
||||||
sds cmd;
|
hisds cmd, aux;
|
||||||
unsigned long long totlen;
|
unsigned long long totlen;
|
||||||
int j;
|
int j;
|
||||||
size_t len;
|
size_t len;
|
||||||
@ -576,32 +585,36 @@ int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Use an SDS string for command construction */
|
/* Use an SDS string for command construction */
|
||||||
cmd = sdsempty();
|
cmd = hi_sdsempty();
|
||||||
if (cmd == NULL)
|
if (cmd == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* We already know how much storage we need */
|
/* We already know how much storage we need */
|
||||||
cmd = sdsMakeRoomFor(cmd, totlen);
|
aux = hi_sdsMakeRoomFor(cmd, totlen);
|
||||||
if (cmd == NULL)
|
if (aux == NULL) {
|
||||||
|
hi_sdsfree(cmd);
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Construct command */
|
|
||||||
cmd = sdscatfmt(cmd, "*%i\r\n", argc);
|
|
||||||
for (j=0; j < argc; j++) {
|
|
||||||
len = argvlen ? argvlen[j] : strlen(argv[j]);
|
|
||||||
cmd = sdscatfmt(cmd, "$%u\r\n", len);
|
|
||||||
cmd = sdscatlen(cmd, argv[j], len);
|
|
||||||
cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(sdslen(cmd)==totlen);
|
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;
|
*target = cmd;
|
||||||
return totlen;
|
return totlen;
|
||||||
}
|
}
|
||||||
|
|
||||||
void redisFreeSdsCommand(sds cmd) {
|
void redisFreeSdsCommand(hisds cmd) {
|
||||||
sdsfree(cmd);
|
hi_sdsfree(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Format a command according to the Redis protocol. This function takes the
|
/* Format a command according to the Redis protocol. This function takes the
|
||||||
@ -627,7 +640,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Build the command at protocol level */
|
/* Build the command at protocol level */
|
||||||
cmd = malloc(totlen+1);
|
cmd = hi_malloc(totlen+1);
|
||||||
if (cmd == NULL)
|
if (cmd == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -648,7 +661,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
|
|||||||
}
|
}
|
||||||
|
|
||||||
void redisFreeCommand(char *cmd) {
|
void redisFreeCommand(char *cmd) {
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void __redisSetError(redisContext *c, int type, const char *str) {
|
void __redisSetError(redisContext *c, int type, const char *str) {
|
||||||
@ -671,15 +684,21 @@ redisReader *redisReaderCreate(void) {
|
|||||||
return redisReaderCreateWithFunctions(&defaultFunctions);
|
return redisReaderCreateWithFunctions(&defaultFunctions);
|
||||||
}
|
}
|
||||||
|
|
||||||
static redisContext *redisContextInit(const redisOptions *options) {
|
static void redisPushAutoFree(void *privdata, void *reply) {
|
||||||
|
(void)privdata;
|
||||||
|
freeReplyObject(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
static redisContext *redisContextInit(void) {
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
|
|
||||||
c = calloc(1, sizeof(*c));
|
c = hi_calloc(1, sizeof(*c));
|
||||||
if (c == NULL)
|
if (c == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
c->funcs = &redisContextDefaultFuncs;
|
c->funcs = &redisContextDefaultFuncs;
|
||||||
c->obuf = sdsempty();
|
|
||||||
|
c->obuf = hi_sdsempty();
|
||||||
c->reader = redisReaderCreate();
|
c->reader = redisReaderCreate();
|
||||||
c->fd = REDIS_INVALID_FD;
|
c->fd = REDIS_INVALID_FD;
|
||||||
|
|
||||||
@ -687,7 +706,7 @@ static redisContext *redisContextInit(const redisOptions *options) {
|
|||||||
redisFree(c);
|
redisFree(c);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
(void)options; /* options are used in other functions */
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,18 +715,23 @@ void redisFree(redisContext *c) {
|
|||||||
return;
|
return;
|
||||||
redisNetClose(c);
|
redisNetClose(c);
|
||||||
|
|
||||||
sdsfree(c->obuf);
|
hi_sdsfree(c->obuf);
|
||||||
redisReaderFree(c->reader);
|
redisReaderFree(c->reader);
|
||||||
free(c->tcp.host);
|
hi_free(c->tcp.host);
|
||||||
free(c->tcp.source_addr);
|
hi_free(c->tcp.source_addr);
|
||||||
free(c->unix_sock.path);
|
hi_free(c->unix_sock.path);
|
||||||
free(c->timeout);
|
hi_free(c->connect_timeout);
|
||||||
free(c->saddr);
|
hi_free(c->command_timeout);
|
||||||
if (c->funcs->free_privdata) {
|
hi_free(c->saddr);
|
||||||
c->funcs->free_privdata(c->privdata);
|
|
||||||
}
|
if (c->privdata && c->free_privdata)
|
||||||
|
c->free_privdata(c->privdata);
|
||||||
|
|
||||||
|
if (c->funcs->free_privctx)
|
||||||
|
c->funcs->free_privctx(c->privctx);
|
||||||
|
|
||||||
memset(c, 0xff, sizeof(*c));
|
memset(c, 0xff, sizeof(*c));
|
||||||
free(c);
|
hi_free(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
redisFD redisFreeKeepFd(redisContext *c) {
|
redisFD redisFreeKeepFd(redisContext *c) {
|
||||||
@ -721,35 +745,46 @@ int redisReconnect(redisContext *c) {
|
|||||||
c->err = 0;
|
c->err = 0;
|
||||||
memset(c->errstr, '\0', strlen(c->errstr));
|
memset(c->errstr, '\0', strlen(c->errstr));
|
||||||
|
|
||||||
if (c->privdata && c->funcs->free_privdata) {
|
if (c->privctx && c->funcs->free_privctx) {
|
||||||
c->funcs->free_privdata(c->privdata);
|
c->funcs->free_privctx(c->privctx);
|
||||||
c->privdata = NULL;
|
c->privctx = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
redisNetClose(c);
|
redisNetClose(c);
|
||||||
|
|
||||||
sdsfree(c->obuf);
|
hi_sdsfree(c->obuf);
|
||||||
redisReaderFree(c->reader);
|
redisReaderFree(c->reader);
|
||||||
|
|
||||||
c->obuf = sdsempty();
|
c->obuf = hi_sdsempty();
|
||||||
c->reader = redisReaderCreate();
|
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) {
|
if (c->connection_type == REDIS_CONN_TCP) {
|
||||||
return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
|
ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
|
||||||
c->timeout, c->tcp.source_addr);
|
c->connect_timeout, c->tcp.source_addr);
|
||||||
} else if (c->connection_type == REDIS_CONN_UNIX) {
|
} else if (c->connection_type == REDIS_CONN_UNIX) {
|
||||||
return redisContextConnectUnix(c, c->unix_sock.path, c->timeout);
|
ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout);
|
||||||
} else {
|
} else {
|
||||||
/* Something bad happened here and shouldn't have. There isn't
|
/* Something bad happened here and shouldn't have. There isn't
|
||||||
enough information in the context to reconnect. */
|
enough information in the context to reconnect. */
|
||||||
__redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
|
__redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
|
||||||
|
ret = REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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 *redisConnectWithOptions(const redisOptions *options) {
|
||||||
redisContext *c = redisContextInit(options);
|
redisContext *c = redisContextInit();
|
||||||
if (c == NULL) {
|
if (c == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -763,13 +798,29 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
|
|||||||
c->flags |= REDIS_NO_AUTO_FREE;
|
c->flags |= REDIS_NO_AUTO_FREE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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) {
|
if (options->type == REDIS_CONN_TCP) {
|
||||||
redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
|
redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
|
||||||
options->endpoint.tcp.port, options->timeout,
|
options->endpoint.tcp.port, options->connect_timeout,
|
||||||
options->endpoint.tcp.source_addr);
|
options->endpoint.tcp.source_addr);
|
||||||
} else if (options->type == REDIS_CONN_UNIX) {
|
} else if (options->type == REDIS_CONN_UNIX) {
|
||||||
redisContextConnectUnix(c, options->endpoint.unix_socket,
|
redisContextConnectUnix(c, options->endpoint.unix_socket,
|
||||||
options->timeout);
|
options->connect_timeout);
|
||||||
} else if (options->type == REDIS_CONN_USERFD) {
|
} else if (options->type == REDIS_CONN_USERFD) {
|
||||||
c->fd = options->endpoint.fd;
|
c->fd = options->endpoint.fd;
|
||||||
c->flags |= REDIS_CONNECTED;
|
c->flags |= REDIS_CONNECTED;
|
||||||
@ -777,9 +828,11 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
|
|||||||
// Unknown type - FIXME - FREE
|
// Unknown type - FIXME - FREE
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
|
|
||||||
redisContextSetTimeout(c, *options->timeout);
|
if (options->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
|
||||||
|
redisContextSetTimeout(c, *options->command_timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -795,7 +848,7 @@ redisContext *redisConnect(const char *ip, int port) {
|
|||||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
|
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
|
||||||
redisOptions options = {0};
|
redisOptions options = {0};
|
||||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||||
options.timeout = &tv;
|
options.connect_timeout = &tv;
|
||||||
return redisConnectWithOptions(&options);
|
return redisConnectWithOptions(&options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,7 +886,7 @@ redisContext *redisConnectUnix(const char *path) {
|
|||||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
|
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
|
||||||
redisOptions options = {0};
|
redisOptions options = {0};
|
||||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||||
options.timeout = &tv;
|
options.connect_timeout = &tv;
|
||||||
return redisConnectWithOptions(&options);
|
return redisConnectWithOptions(&options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -865,6 +918,13 @@ int redisEnableKeepAlive(redisContext *c) {
|
|||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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
|
/* 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.
|
* and read some bytes from the socket and feed them to the reply parser.
|
||||||
*
|
*
|
||||||
@ -906,21 +966,27 @@ int redisBufferWrite(redisContext *c, int *done) {
|
|||||||
if (c->err)
|
if (c->err)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
if (sdslen(c->obuf) > 0) {
|
if (hi_sdslen(c->obuf) > 0) {
|
||||||
int nwritten = c->funcs->write(c);
|
ssize_t nwritten = c->funcs->write(c);
|
||||||
if (nwritten < 0) {
|
if (nwritten < 0) {
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
} else if (nwritten > 0) {
|
} else if (nwritten > 0) {
|
||||||
if (nwritten == (signed)sdslen(c->obuf)) {
|
if (nwritten == (ssize_t)hi_sdslen(c->obuf)) {
|
||||||
sdsfree(c->obuf);
|
hi_sdsfree(c->obuf);
|
||||||
c->obuf = sdsempty();
|
c->obuf = hi_sdsempty();
|
||||||
|
if (c->obuf == NULL)
|
||||||
|
goto oom;
|
||||||
} else {
|
} else {
|
||||||
sdsrange(c->obuf,nwritten,-1);
|
if (hi_sdsrange(c->obuf,nwritten,-1) < 0) goto oom;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (done != NULL) *done = (sdslen(c->obuf) == 0);
|
if (done != NULL) *done = (hi_sdslen(c->obuf) == 0);
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
|
|
||||||
|
oom:
|
||||||
|
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||||
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Internal helper function to try and get a reply from the reader,
|
/* Internal helper function to try and get a reply from the reader,
|
||||||
@ -930,9 +996,21 @@ int redisGetReplyFromReader(redisContext *c, void **reply) {
|
|||||||
__redisSetError(c,c->reader->err,c->reader->errstr);
|
__redisSetError(c,c->reader->err,c->reader->errstr);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
int redisGetReply(redisContext *c, void **reply) {
|
int redisGetReply(redisContext *c, void **reply) {
|
||||||
int wdone = 0;
|
int wdone = 0;
|
||||||
void *aux = NULL;
|
void *aux = NULL;
|
||||||
@ -953,13 +1031,23 @@ int redisGetReply(redisContext *c, void **reply) {
|
|||||||
do {
|
do {
|
||||||
if (redisBufferRead(c) == REDIS_ERR)
|
if (redisBufferRead(c) == REDIS_ERR)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
|
/* We loop here in case the user has specified a RESP3
|
||||||
|
* PUSH handler (e.g. for client tracking). */
|
||||||
|
do {
|
||||||
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
|
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
} while (redisHandledPushReply(c, aux));
|
||||||
} while (aux == NULL);
|
} while (aux == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set reply object */
|
/* Set reply or free it if we were passed NULL */
|
||||||
if (reply != NULL) *reply = aux;
|
if (reply != NULL) {
|
||||||
|
*reply = aux;
|
||||||
|
} else {
|
||||||
|
freeReplyObject(aux);
|
||||||
|
}
|
||||||
|
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -971,9 +1059,9 @@ int redisGetReply(redisContext *c, void **reply) {
|
|||||||
* the reply (or replies in pub/sub).
|
* the reply (or replies in pub/sub).
|
||||||
*/
|
*/
|
||||||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {
|
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {
|
||||||
sds newbuf;
|
hisds newbuf;
|
||||||
|
|
||||||
newbuf = sdscatlen(c->obuf,cmd,len);
|
newbuf = hi_sdscatlen(c->obuf,cmd,len);
|
||||||
if (newbuf == NULL) {
|
if (newbuf == NULL) {
|
||||||
__redisSetError(c,REDIS_ERR_OOM,"Out of memory");
|
__redisSetError(c,REDIS_ERR_OOM,"Out of memory");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -1006,11 +1094,11 @@ int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
|
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1025,7 +1113,7 @@ int redisAppendCommand(redisContext *c, const char *format, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
|
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
|
||||||
sds cmd;
|
hisds cmd;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
||||||
@ -1035,11 +1123,11 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
|
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
|
||||||
sdsfree(cmd);
|
hi_sdsfree(cmd);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
sdsfree(cmd);
|
hi_sdsfree(cmd);
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
deps/hiredis/hiredis.h
vendored
69
deps/hiredis/hiredis.h
vendored
@ -39,14 +39,16 @@
|
|||||||
#include <sys/time.h> /* for struct timeval */
|
#include <sys/time.h> /* for struct timeval */
|
||||||
#else
|
#else
|
||||||
struct timeval; /* forward declaration */
|
struct timeval; /* forward declaration */
|
||||||
|
typedef long long ssize_t;
|
||||||
#endif
|
#endif
|
||||||
#include <stdint.h> /* uintXX_t, etc */
|
#include <stdint.h> /* uintXX_t, etc */
|
||||||
#include "sds.h" /* for sds */
|
#include "sds.h" /* for hisds */
|
||||||
|
#include "alloc.h" /* for allocation wrappers */
|
||||||
|
|
||||||
#define HIREDIS_MAJOR 0
|
#define HIREDIS_MAJOR 1
|
||||||
#define HIREDIS_MINOR 14
|
#define HIREDIS_MINOR 0
|
||||||
#define HIREDIS_PATCH 0
|
#define HIREDIS_PATCH 0
|
||||||
#define HIREDIS_SONAME 0.14
|
#define HIREDIS_SONAME 1.0.0
|
||||||
|
|
||||||
/* Connection type can be blocking or non-blocking and is set in the
|
/* Connection type can be blocking or non-blocking and is set in the
|
||||||
* least significant bit of the flags field in redisContext. */
|
* least significant bit of the flags field in redisContext. */
|
||||||
@ -90,6 +92,15 @@ struct timeval; /* forward declaration */
|
|||||||
* SO_REUSEADDR is being used. */
|
* SO_REUSEADDR is being used. */
|
||||||
#define REDIS_CONNECT_RETRIES 10
|
#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
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@ -101,7 +112,7 @@ typedef struct redisReply {
|
|||||||
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
|
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
|
||||||
size_t len; /* Length of string */
|
size_t len; /* Length of string */
|
||||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||||
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
REDIS_REPLY_VERB, and REDIS_REPLY_DOUBLE (in additional to dval). */
|
||||||
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
|
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
|
||||||
terminated 3 character content type, such as "txt". */
|
terminated 3 character content type, such as "txt". */
|
||||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||||
@ -117,9 +128,9 @@ void freeReplyObject(void *reply);
|
|||||||
int redisvFormatCommand(char **target, const char *format, va_list ap);
|
int redisvFormatCommand(char **target, const char *format, va_list ap);
|
||||||
int redisFormatCommand(char **target, const char *format, ...);
|
int redisFormatCommand(char **target, const char *format, ...);
|
||||||
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
|
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
|
||||||
int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
|
int redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen);
|
||||||
void redisFreeCommand(char *cmd);
|
void redisFreeCommand(char *cmd);
|
||||||
void redisFreeSdsCommand(sds cmd);
|
void redisFreeSdsCommand(hisds cmd);
|
||||||
|
|
||||||
enum redisConnectionType {
|
enum redisConnectionType {
|
||||||
REDIS_CONN_TCP,
|
REDIS_CONN_TCP,
|
||||||
@ -138,6 +149,9 @@ struct redisSsl;
|
|||||||
*/
|
*/
|
||||||
#define REDIS_OPT_NOAUTOFREE 0x04
|
#define REDIS_OPT_NOAUTOFREE 0x04
|
||||||
|
|
||||||
|
/* Don't automatically intercept and free RESP3 PUSH replies. */
|
||||||
|
#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08
|
||||||
|
|
||||||
/* In Unix systems a file descriptor is a regular signed int, with -1
|
/* In Unix systems a file descriptor is a regular signed int, with -1
|
||||||
* representing an invalid descriptor. In Windows it is a SOCKET
|
* representing an invalid descriptor. In Windows it is a SOCKET
|
||||||
* (32- or 64-bit unsigned integer depending on the architecture), where
|
* (32- or 64-bit unsigned integer depending on the architecture), where
|
||||||
@ -162,8 +176,11 @@ typedef struct {
|
|||||||
int type;
|
int type;
|
||||||
/* bit field of REDIS_OPT_xxx */
|
/* bit field of REDIS_OPT_xxx */
|
||||||
int options;
|
int options;
|
||||||
/* timeout value. if NULL, no timeout is used */
|
/* timeout value for connect operation. If NULL, no timeout is used */
|
||||||
const struct timeval *timeout;
|
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 {
|
union {
|
||||||
/** use this field for tcp/ip connections */
|
/** use this field for tcp/ip connections */
|
||||||
struct {
|
struct {
|
||||||
@ -178,6 +195,14 @@ typedef struct {
|
|||||||
* file descriptor */
|
* file descriptor */
|
||||||
redisFD fd;
|
redisFD fd;
|
||||||
} endpoint;
|
} 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;
|
} redisOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -192,15 +217,16 @@ typedef struct {
|
|||||||
(opts)->type = REDIS_CONN_UNIX; \
|
(opts)->type = REDIS_CONN_UNIX; \
|
||||||
(opts)->endpoint.unix_socket = path;
|
(opts)->endpoint.unix_socket = path;
|
||||||
|
|
||||||
struct redisAsyncContext;
|
#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) \
|
||||||
struct redisContext;
|
(opts)->privdata = data; \
|
||||||
|
(opts)->free_privdata = dtor; \
|
||||||
|
|
||||||
typedef struct redisContextFuncs {
|
typedef struct redisContextFuncs {
|
||||||
void (*free_privdata)(void *);
|
void (*free_privctx)(void *);
|
||||||
void (*async_read)(struct redisAsyncContext *);
|
void (*async_read)(struct redisAsyncContext *);
|
||||||
void (*async_write)(struct redisAsyncContext *);
|
void (*async_write)(struct redisAsyncContext *);
|
||||||
int (*read)(struct redisContext *, char *, size_t);
|
ssize_t (*read)(struct redisContext *, char *, size_t);
|
||||||
int (*write)(struct redisContext *);
|
ssize_t (*write)(struct redisContext *);
|
||||||
} redisContextFuncs;
|
} redisContextFuncs;
|
||||||
|
|
||||||
/* Context for a connection to Redis */
|
/* Context for a connection to Redis */
|
||||||
@ -215,7 +241,8 @@ typedef struct redisContext {
|
|||||||
redisReader *reader; /* Protocol reader */
|
redisReader *reader; /* Protocol reader */
|
||||||
|
|
||||||
enum redisConnectionType connection_type;
|
enum redisConnectionType connection_type;
|
||||||
struct timeval *timeout;
|
struct timeval *connect_timeout;
|
||||||
|
struct timeval *command_timeout;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
char *host;
|
char *host;
|
||||||
@ -231,8 +258,17 @@ typedef struct redisContext {
|
|||||||
struct sockadr *saddr;
|
struct sockadr *saddr;
|
||||||
size_t addrlen;
|
size_t addrlen;
|
||||||
|
|
||||||
/* Additional private data for hiredis addons such as SSL */
|
/* Optional data and corresponding destructor users can use to provide
|
||||||
|
* context to a given redisContext. Not used by hiredis. */
|
||||||
void *privdata;
|
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;
|
||||||
|
|
||||||
redisContext *redisConnectWithOptions(const redisOptions *options);
|
redisContext *redisConnectWithOptions(const redisOptions *options);
|
||||||
@ -259,6 +295,7 @@ redisContext *redisConnectFd(redisFD fd);
|
|||||||
*/
|
*/
|
||||||
int redisReconnect(redisContext *c);
|
int redisReconnect(redisContext *c);
|
||||||
|
|
||||||
|
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);
|
||||||
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||||
int redisEnableKeepAlive(redisContext *c);
|
int redisEnableKeepAlive(redisContext *c);
|
||||||
void redisFree(redisContext *c);
|
void redisFree(redisContext *c);
|
||||||
|
3
deps/hiredis/hiredis.pc.in
vendored
3
deps/hiredis/hiredis.pc.in
vendored
@ -1,6 +1,7 @@
|
|||||||
prefix=@CMAKE_INSTALL_PREFIX@
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
|
install_libdir=@CMAKE_INSTALL_LIBDIR@
|
||||||
exec_prefix=${prefix}
|
exec_prefix=${prefix}
|
||||||
libdir=${exec_prefix}/lib
|
libdir=${exec_prefix}/${install_libdir}
|
||||||
includedir=${prefix}/include
|
includedir=${prefix}/include
|
||||||
pkgincludedir=${includedir}/hiredis
|
pkgincludedir=${includedir}/hiredis
|
||||||
|
|
||||||
|
13
deps/hiredis/hiredis_ssl-config.cmake.in
vendored
Normal file
13
deps/hiredis/hiredis_ssl-config.cmake.in
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
86
deps/hiredis/hiredis_ssl.h
vendored
86
deps/hiredis/hiredis_ssl.h
vendored
@ -32,22 +32,96 @@
|
|||||||
#ifndef __HIREDIS_SSL_H
|
#ifndef __HIREDIS_SSL_H
|
||||||
#define __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
|
/* This is the underlying struct for SSL in ssl.h, which is not included to
|
||||||
* keep build dependencies short here.
|
* keep build dependencies short here.
|
||||||
*/
|
*/
|
||||||
struct ssl_st;
|
struct ssl_st;
|
||||||
|
|
||||||
/**
|
/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
|
||||||
* Secure the connection using SSL. This should be done before any command is
|
* calling OpenSSL.
|
||||||
* executed on the connection.
|
|
||||||
*/
|
*/
|
||||||
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
|
typedef struct redisSSLContext redisSSLContext;
|
||||||
const char *keypath, const char *servername);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate SSL/TLS negotiation on a provided context.
|
* 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_PRIVATE_KEY_LOAD_FAILED /* Failed to load private key */
|
||||||
|
} redisSSLContextError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* __HIREDIS_SSL_H */
|
#endif /* __HIREDIS_SSL_H */
|
||||||
|
119
deps/hiredis/net.c
vendored
119
deps/hiredis/net.c
vendored
@ -57,8 +57,8 @@ void redisNetClose(redisContext *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||||
int nread = recv(c->fd, buf, bufcap, 0);
|
ssize_t nread = recv(c->fd, buf, bufcap, 0);
|
||||||
if (nread == -1) {
|
if (nread == -1) {
|
||||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||||
/* Try again later */
|
/* Try again later */
|
||||||
@ -79,8 +79,8 @@ int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisNetWrite(redisContext *c) {
|
ssize_t redisNetWrite(redisContext *c) {
|
||||||
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
|
ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0);
|
||||||
if (nwritten < 0) {
|
if (nwritten < 0) {
|
||||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||||
/* Try again later */
|
/* Try again later */
|
||||||
@ -203,7 +203,7 @@ int redisKeepAlive(redisContext *c, int interval) {
|
|||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int redisSetTcpNoDelay(redisContext *c) {
|
int redisSetTcpNoDelay(redisContext *c) {
|
||||||
int yes = 1;
|
int yes = 1;
|
||||||
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
||||||
@ -217,7 +217,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
|
|||||||
|
|
||||||
static int redisContextTimeoutMsec(redisContext *c, long *result)
|
static int redisContextTimeoutMsec(redisContext *c, long *result)
|
||||||
{
|
{
|
||||||
const struct timeval *timeout = c->timeout;
|
const struct timeval *timeout = c->connect_timeout;
|
||||||
long msec = -1;
|
long msec = -1;
|
||||||
|
|
||||||
/* Only use timeout when not NULL. */
|
/* Only use timeout when not NULL. */
|
||||||
@ -316,11 +316,7 @@ int redisCheckSocketError(redisContext *c) {
|
|||||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||||
const void *to_ptr = &tv;
|
const void *to_ptr = &tv;
|
||||||
size_t to_sz = sizeof(tv);
|
size_t to_sz = sizeof(tv);
|
||||||
#ifdef _WIN32
|
|
||||||
DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
|
||||||
to_ptr = &timeout_msec;
|
|
||||||
to_sz = sizeof(timeout_msec);
|
|
||||||
#endif
|
|
||||||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
|
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -332,6 +328,38 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
|||||||
return REDIS_OK;
|
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,
|
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||||
const struct timeval *timeout,
|
const struct timeval *timeout,
|
||||||
const char *source_addr) {
|
const char *source_addr) {
|
||||||
@ -356,21 +384,19 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
|||||||
* This is a bit ugly, but atleast it works and doesn't leak memory.
|
* This is a bit ugly, but atleast it works and doesn't leak memory.
|
||||||
**/
|
**/
|
||||||
if (c->tcp.host != addr) {
|
if (c->tcp.host != addr) {
|
||||||
free(c->tcp.host);
|
hi_free(c->tcp.host);
|
||||||
|
|
||||||
c->tcp.host = strdup(addr);
|
c->tcp.host = hi_strdup(addr);
|
||||||
|
if (c->tcp.host == NULL)
|
||||||
|
goto oom;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
if (c->timeout != timeout) {
|
if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
|
||||||
if (c->timeout == NULL)
|
goto oom;
|
||||||
c->timeout = malloc(sizeof(struct timeval));
|
|
||||||
|
|
||||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
free(c->timeout);
|
hi_free(c->connect_timeout);
|
||||||
c->timeout = NULL;
|
c->connect_timeout = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
|
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
|
||||||
@ -379,11 +405,11 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (source_addr == NULL) {
|
if (source_addr == NULL) {
|
||||||
free(c->tcp.source_addr);
|
hi_free(c->tcp.source_addr);
|
||||||
c->tcp.source_addr = NULL;
|
c->tcp.source_addr = NULL;
|
||||||
} else if (c->tcp.source_addr != source_addr) {
|
} else if (c->tcp.source_addr != source_addr) {
|
||||||
free(c->tcp.source_addr);
|
hi_free(c->tcp.source_addr);
|
||||||
c->tcp.source_addr = strdup(source_addr);
|
c->tcp.source_addr = hi_strdup(source_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(_port, 6, "%d", port);
|
snprintf(_port, 6, "%d", port);
|
||||||
@ -446,8 +472,11 @@ addrretry:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* For repeat connection */
|
/* For repeat connection */
|
||||||
free(c->saddr);
|
hi_free(c->saddr);
|
||||||
c->saddr = malloc(p->ai_addrlen);
|
c->saddr = hi_malloc(p->ai_addrlen);
|
||||||
|
if (c->saddr == NULL)
|
||||||
|
goto oom;
|
||||||
|
|
||||||
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
||||||
c->addrlen = p->ai_addrlen;
|
c->addrlen = p->ai_addrlen;
|
||||||
|
|
||||||
@ -474,12 +503,12 @@ addrretry:
|
|||||||
wait_for_ready:
|
wait_for_ready:
|
||||||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||||
goto error;
|
goto error;
|
||||||
|
if (redisSetTcpNoDelay(c) != REDIS_OK)
|
||||||
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (blocking && redisSetBlocking(c,1) != REDIS_OK)
|
if (blocking && redisSetBlocking(c,1) != REDIS_OK)
|
||||||
goto error;
|
goto error;
|
||||||
if (redisSetTcpNoDelay(c) != REDIS_OK)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
c->flags |= REDIS_CONNECTED;
|
c->flags |= REDIS_CONNECTED;
|
||||||
rv = REDIS_OK;
|
rv = REDIS_OK;
|
||||||
@ -492,6 +521,8 @@ addrretry:
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oom:
|
||||||
|
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||||
error:
|
error:
|
||||||
rv = REDIS_ERR;
|
rv = REDIS_ERR;
|
||||||
end:
|
end:
|
||||||
@ -525,25 +556,32 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
c->connection_type = REDIS_CONN_UNIX;
|
c->connection_type = REDIS_CONN_UNIX;
|
||||||
if (c->unix_sock.path != path)
|
if (c->unix_sock.path != path) {
|
||||||
c->unix_sock.path = strdup(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 (timeout) {
|
||||||
if (c->timeout != timeout) {
|
if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
|
||||||
if (c->timeout == NULL)
|
goto oom;
|
||||||
c->timeout = malloc(sizeof(struct timeval));
|
|
||||||
|
|
||||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
free(c->timeout);
|
hi_free(c->connect_timeout);
|
||||||
c->timeout = NULL;
|
c->connect_timeout = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
|
/* 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);
|
c->addrlen = sizeof(struct sockaddr_un);
|
||||||
sa->sun_family = AF_UNIX;
|
sa->sun_family = AF_UNIX;
|
||||||
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
|
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
|
||||||
@ -568,4 +606,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
|||||||
errno = EPROTONOSUPPORT;
|
errno = EPROTONOSUPPORT;
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
#endif /* _WIN32 */
|
#endif /* _WIN32 */
|
||||||
|
oom:
|
||||||
|
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||||
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
6
deps/hiredis/net.h
vendored
6
deps/hiredis/net.h
vendored
@ -38,8 +38,8 @@
|
|||||||
#include "hiredis.h"
|
#include "hiredis.h"
|
||||||
|
|
||||||
void redisNetClose(redisContext *c);
|
void redisNetClose(redisContext *c);
|
||||||
int redisNetRead(redisContext *c, char *buf, size_t bufcap);
|
ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap);
|
||||||
int redisNetWrite(redisContext *c);
|
ssize_t redisNetWrite(redisContext *c);
|
||||||
|
|
||||||
int redisCheckSocketError(redisContext *c);
|
int redisCheckSocketError(redisContext *c);
|
||||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
||||||
@ -51,4 +51,6 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
|||||||
int redisKeepAlive(redisContext *c, int interval);
|
int redisKeepAlive(redisContext *c, int interval);
|
||||||
int redisCheckConnectDone(redisContext *c, int *completed);
|
int redisCheckConnectDone(redisContext *c, int *completed);
|
||||||
|
|
||||||
|
int redisSetTcpNoDelay(redisContext *c);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
156
deps/hiredis/read.c
vendored
156
deps/hiredis/read.c
vendored
@ -42,10 +42,14 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "alloc.h"
|
||||||
#include "read.h"
|
#include "read.h"
|
||||||
#include "sds.h"
|
#include "sds.h"
|
||||||
#include "win32.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) {
|
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Clear input buffer on errors. */
|
/* Clear input buffer on errors. */
|
||||||
sdsfree(r->buf);
|
hi_sdsfree(r->buf);
|
||||||
r->buf = NULL;
|
r->buf = NULL;
|
||||||
r->pos = r->len = 0;
|
r->pos = r->len = 0;
|
||||||
|
|
||||||
@ -243,11 +247,12 @@ static void moveToNextTask(redisReader *r) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cur = &(r->rstack[r->ridx]);
|
cur = r->task[r->ridx];
|
||||||
prv = &(r->rstack[r->ridx-1]);
|
prv = r->task[r->ridx-1];
|
||||||
assert(prv->type == REDIS_REPLY_ARRAY ||
|
assert(prv->type == REDIS_REPLY_ARRAY ||
|
||||||
prv->type == REDIS_REPLY_MAP ||
|
prv->type == REDIS_REPLY_MAP ||
|
||||||
prv->type == REDIS_REPLY_SET);
|
prv->type == REDIS_REPLY_SET ||
|
||||||
|
prv->type == REDIS_REPLY_PUSH);
|
||||||
if (cur->idx == prv->elements-1) {
|
if (cur->idx == prv->elements-1) {
|
||||||
r->ridx--;
|
r->ridx--;
|
||||||
} else {
|
} else {
|
||||||
@ -262,7 +267,7 @@ static void moveToNextTask(redisReader *r) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int processLineItem(redisReader *r) {
|
static int processLineItem(redisReader *r) {
|
||||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
redisReadTask *cur = r->task[r->ridx];
|
||||||
void *obj;
|
void *obj;
|
||||||
char *p;
|
char *p;
|
||||||
int len;
|
int len;
|
||||||
@ -297,7 +302,7 @@ static int processLineItem(redisReader *r) {
|
|||||||
if (strcasecmp(buf,",inf") == 0) {
|
if (strcasecmp(buf,",inf") == 0) {
|
||||||
d = INFINITY; /* Positive infinite. */
|
d = INFINITY; /* Positive infinite. */
|
||||||
} else if (strcasecmp(buf,",-inf") == 0) {
|
} else if (strcasecmp(buf,",-inf") == 0) {
|
||||||
d = -INFINITY; /* Nevative infinite. */
|
d = -INFINITY; /* Negative infinite. */
|
||||||
} else {
|
} else {
|
||||||
d = strtod((char*)buf,&eptr);
|
d = strtod((char*)buf,&eptr);
|
||||||
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
||||||
@ -344,7 +349,7 @@ static int processLineItem(redisReader *r) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int processBulkItem(redisReader *r) {
|
static int processBulkItem(redisReader *r) {
|
||||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
redisReadTask *cur = r->task[r->ridx];
|
||||||
void *obj = NULL;
|
void *obj = NULL;
|
||||||
char *p, *s;
|
char *p, *s;
|
||||||
long long len;
|
long long len;
|
||||||
@ -415,18 +420,42 @@ static int processBulkItem(redisReader *r) {
|
|||||||
return REDIS_ERR;
|
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. */
|
/* Process the array, map and set types. */
|
||||||
static int processAggregateItem(redisReader *r) {
|
static int processAggregateItem(redisReader *r) {
|
||||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
redisReadTask *cur = r->task[r->ridx];
|
||||||
void *obj;
|
void *obj;
|
||||||
char *p;
|
char *p;
|
||||||
long long elements;
|
long long elements;
|
||||||
int root = 0, len;
|
int root = 0, len;
|
||||||
|
|
||||||
/* Set error for nested multi bulks with depth > 7 */
|
/* Set error for nested multi bulks with depth > 7 */
|
||||||
if (r->ridx == 8) {
|
if (r->ridx == r->tasks - 1) {
|
||||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
if (redisReaderGrow(r) == REDIS_ERR)
|
||||||
"No support for nested multi bulk replies with depth > 7");
|
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,7 +468,9 @@ static int processAggregateItem(redisReader *r) {
|
|||||||
|
|
||||||
root = (r->ridx == 0);
|
root = (r->ridx == 0);
|
||||||
|
|
||||||
if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) {
|
if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) ||
|
||||||
|
(r->maxelements > 0 && elements > r->maxelements))
|
||||||
|
{
|
||||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
"Multi-bulk length out of range");
|
"Multi-bulk length out of range");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -475,12 +506,12 @@ static int processAggregateItem(redisReader *r) {
|
|||||||
cur->elements = elements;
|
cur->elements = elements;
|
||||||
cur->obj = obj;
|
cur->obj = obj;
|
||||||
r->ridx++;
|
r->ridx++;
|
||||||
r->rstack[r->ridx].type = -1;
|
r->task[r->ridx]->type = -1;
|
||||||
r->rstack[r->ridx].elements = -1;
|
r->task[r->ridx]->elements = -1;
|
||||||
r->rstack[r->ridx].idx = 0;
|
r->task[r->ridx]->idx = 0;
|
||||||
r->rstack[r->ridx].obj = NULL;
|
r->task[r->ridx]->obj = NULL;
|
||||||
r->rstack[r->ridx].parent = cur;
|
r->task[r->ridx]->parent = cur;
|
||||||
r->rstack[r->ridx].privdata = r->privdata;
|
r->task[r->ridx]->privdata = r->privdata;
|
||||||
} else {
|
} else {
|
||||||
moveToNextTask(r);
|
moveToNextTask(r);
|
||||||
}
|
}
|
||||||
@ -495,7 +526,7 @@ static int processAggregateItem(redisReader *r) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int processItem(redisReader *r) {
|
static int processItem(redisReader *r) {
|
||||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
redisReadTask *cur = r->task[r->ridx];
|
||||||
char *p;
|
char *p;
|
||||||
|
|
||||||
/* check if we need to read type */
|
/* check if we need to read type */
|
||||||
@ -535,6 +566,9 @@ static int processItem(redisReader *r) {
|
|||||||
case '=':
|
case '=':
|
||||||
cur->type = REDIS_REPLY_VERB;
|
cur->type = REDIS_REPLY_VERB;
|
||||||
break;
|
break;
|
||||||
|
case '>':
|
||||||
|
cur->type = REDIS_REPLY_PUSH;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
__redisReaderSetErrorProtocolByte(r,*p);
|
__redisReaderSetErrorProtocolByte(r,*p);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -560,6 +594,7 @@ static int processItem(redisReader *r) {
|
|||||||
case REDIS_REPLY_ARRAY:
|
case REDIS_REPLY_ARRAY:
|
||||||
case REDIS_REPLY_MAP:
|
case REDIS_REPLY_MAP:
|
||||||
case REDIS_REPLY_SET:
|
case REDIS_REPLY_SET:
|
||||||
|
case REDIS_REPLY_PUSH:
|
||||||
return processAggregateItem(r);
|
return processAggregateItem(r);
|
||||||
default:
|
default:
|
||||||
assert(NULL);
|
assert(NULL);
|
||||||
@ -570,33 +605,57 @@ static int processItem(redisReader *r) {
|
|||||||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||||
redisReader *r;
|
redisReader *r;
|
||||||
|
|
||||||
r = calloc(1,sizeof(redisReader));
|
r = hi_calloc(1,sizeof(redisReader));
|
||||||
if (r == NULL)
|
if (r == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
r->fn = fn;
|
r->buf = hi_sdsempty();
|
||||||
r->buf = sdsempty();
|
if (r->buf == NULL)
|
||||||
r->maxbuf = REDIS_READER_MAX_BUF;
|
goto oom;
|
||||||
if (r->buf == NULL) {
|
|
||||||
free(r);
|
r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task));
|
||||||
return NULL;
|
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;
|
r->ridx = -1;
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
|
oom:
|
||||||
|
redisReaderFree(r);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void redisReaderFree(redisReader *r) {
|
void redisReaderFree(redisReader *r) {
|
||||||
if (r == NULL)
|
if (r == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
||||||
r->fn->freeObject(r->reply);
|
r->fn->freeObject(r->reply);
|
||||||
sdsfree(r->buf);
|
|
||||||
free(r);
|
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) {
|
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
|
||||||
sds newbuf;
|
hisds newbuf;
|
||||||
|
|
||||||
/* Return early when this reader is in an erroneous state. */
|
/* Return early when this reader is in an erroneous state. */
|
||||||
if (r->err)
|
if (r->err)
|
||||||
@ -605,26 +664,25 @@ int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
|
|||||||
/* Copy the provided buffer. */
|
/* Copy the provided buffer. */
|
||||||
if (buf != NULL && len >= 1) {
|
if (buf != NULL && len >= 1) {
|
||||||
/* Destroy internal buffer when it is empty and is quite large. */
|
/* Destroy internal buffer when it is empty and is quite large. */
|
||||||
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
|
if (r->len == 0 && r->maxbuf != 0 && hi_sdsavail(r->buf) > r->maxbuf) {
|
||||||
sdsfree(r->buf);
|
hi_sdsfree(r->buf);
|
||||||
r->buf = sdsempty();
|
r->buf = hi_sdsempty();
|
||||||
|
if (r->buf == 0) goto oom;
|
||||||
|
|
||||||
r->pos = 0;
|
r->pos = 0;
|
||||||
|
|
||||||
/* r->buf should not be NULL since we just free'd a larger one. */
|
|
||||||
assert(r->buf != NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newbuf = sdscatlen(r->buf,buf,len);
|
newbuf = hi_sdscatlen(r->buf,buf,len);
|
||||||
if (newbuf == NULL) {
|
if (newbuf == NULL) goto oom;
|
||||||
__redisReaderSetErrorOOM(r);
|
|
||||||
return REDIS_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
r->buf = newbuf;
|
r->buf = newbuf;
|
||||||
r->len = sdslen(r->buf);
|
r->len = hi_sdslen(r->buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
|
oom:
|
||||||
|
__redisReaderSetErrorOOM(r);
|
||||||
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisReaderGetReply(redisReader *r, void **reply) {
|
int redisReaderGetReply(redisReader *r, void **reply) {
|
||||||
@ -642,12 +700,12 @@ int redisReaderGetReply(redisReader *r, void **reply) {
|
|||||||
|
|
||||||
/* Set first item to process when the stack is empty. */
|
/* Set first item to process when the stack is empty. */
|
||||||
if (r->ridx == -1) {
|
if (r->ridx == -1) {
|
||||||
r->rstack[0].type = -1;
|
r->task[0]->type = -1;
|
||||||
r->rstack[0].elements = -1;
|
r->task[0]->elements = -1;
|
||||||
r->rstack[0].idx = -1;
|
r->task[0]->idx = -1;
|
||||||
r->rstack[0].obj = NULL;
|
r->task[0]->obj = NULL;
|
||||||
r->rstack[0].parent = NULL;
|
r->task[0]->parent = NULL;
|
||||||
r->rstack[0].privdata = r->privdata;
|
r->task[0]->privdata = r->privdata;
|
||||||
r->ridx = 0;
|
r->ridx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -663,9 +721,9 @@ int redisReaderGetReply(redisReader *r, void **reply) {
|
|||||||
/* Discard part of the buffer when we've consumed at least 1k, to avoid
|
/* Discard part of the buffer when we've consumed at least 1k, to avoid
|
||||||
* doing unnecessary calls to memmove() in sds.c. */
|
* doing unnecessary calls to memmove() in sds.c. */
|
||||||
if (r->pos >= 1024) {
|
if (r->pos >= 1024) {
|
||||||
sdsrange(r->buf,r->pos,-1);
|
if (hi_sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR;
|
||||||
r->pos = 0;
|
r->pos = 0;
|
||||||
r->len = sdslen(r->buf);
|
r->len = hi_sdslen(r->buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Emit a reply when there is one. */
|
/* Emit a reply when there is one. */
|
||||||
|
13
deps/hiredis/read.h
vendored
13
deps/hiredis/read.h
vendored
@ -63,7 +63,11 @@
|
|||||||
#define REDIS_REPLY_BIGNUM 13
|
#define REDIS_REPLY_BIGNUM 13
|
||||||
#define REDIS_REPLY_VERB 14
|
#define REDIS_REPLY_VERB 14
|
||||||
|
|
||||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
/* 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
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -71,7 +75,7 @@ extern "C" {
|
|||||||
|
|
||||||
typedef struct redisReadTask {
|
typedef struct redisReadTask {
|
||||||
int type;
|
int type;
|
||||||
int elements; /* number of elements in multibulk container */
|
long long elements; /* number of elements in multibulk container */
|
||||||
int idx; /* index in parent (array) object */
|
int idx; /* index in parent (array) object */
|
||||||
void *obj; /* holds user-generated value for a read task */
|
void *obj; /* holds user-generated value for a read task */
|
||||||
struct redisReadTask *parent; /* parent task */
|
struct redisReadTask *parent; /* parent task */
|
||||||
@ -96,8 +100,11 @@ typedef struct redisReader {
|
|||||||
size_t pos; /* Buffer cursor */
|
size_t pos; /* Buffer cursor */
|
||||||
size_t len; /* Buffer length */
|
size_t len; /* Buffer length */
|
||||||
size_t maxbuf; /* Max length of unused buffer */
|
size_t maxbuf; /* Max length of unused buffer */
|
||||||
|
long long maxelements; /* Max multi-bulk elements */
|
||||||
|
|
||||||
|
redisReadTask **task;
|
||||||
|
int tasks;
|
||||||
|
|
||||||
redisReadTask rstack[9];
|
|
||||||
int ridx; /* Index of current read task */
|
int ridx; /* Index of current read task */
|
||||||
void *reply; /* Temporary reply pointer */
|
void *reply; /* Temporary reply pointer */
|
||||||
|
|
||||||
|
828
deps/hiredis/sds.c
vendored
828
deps/hiredis/sds.c
vendored
File diff suppressed because it is too large
Load Diff
264
deps/hiredis/sds.h
vendored
264
deps/hiredis/sds.h
vendored
@ -30,29 +30,31 @@
|
|||||||
* POSSIBILITY OF SUCH DAMAGE.
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __SDS_H
|
#ifndef HIREDIS_SDS_H
|
||||||
#define __SDS_H
|
#define HIREDIS_SDS_H
|
||||||
|
|
||||||
#define SDS_MAX_PREALLOC (1024*1024)
|
#define HI_SDS_MAX_PREALLOC (1024*1024)
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#define __attribute__(x)
|
#define __attribute__(x)
|
||||||
|
typedef long long ssize_t;
|
||||||
|
#define SSIZE_MAX (LLONG_MAX >> 1)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef char *sds;
|
typedef char *hisds;
|
||||||
|
|
||||||
/* Note: sdshdr5 is never used, we just access the flags byte directly.
|
/* Note: sdshdr5 is never used, we just access the flags byte directly.
|
||||||
* However is here to document the layout of type 5 SDS strings. */
|
* However is here to document the layout of type 5 SDS strings. */
|
||||||
struct __attribute__ ((__packed__)) sdshdr5 {
|
struct __attribute__ ((__packed__)) hisdshdr5 {
|
||||||
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
|
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
|
||||||
#ifndef __cplusplus
|
#ifndef __cplusplus
|
||||||
char buf[];
|
char buf[];
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct __attribute__ ((__packed__)) sdshdr8 {
|
struct __attribute__ ((__packed__)) hisdshdr8 {
|
||||||
uint8_t len; /* used */
|
uint8_t len; /* used */
|
||||||
uint8_t alloc; /* excluding the header and null terminator */
|
uint8_t alloc; /* excluding the header and null terminator */
|
||||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||||
@ -60,7 +62,7 @@ struct __attribute__ ((__packed__)) sdshdr8 {
|
|||||||
char buf[];
|
char buf[];
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct __attribute__ ((__packed__)) sdshdr16 {
|
struct __attribute__ ((__packed__)) hisdshdr16 {
|
||||||
uint16_t len; /* used */
|
uint16_t len; /* used */
|
||||||
uint16_t alloc; /* excluding the header and null terminator */
|
uint16_t alloc; /* excluding the header and null terminator */
|
||||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||||
@ -68,7 +70,7 @@ struct __attribute__ ((__packed__)) sdshdr16 {
|
|||||||
char buf[];
|
char buf[];
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct __attribute__ ((__packed__)) sdshdr32 {
|
struct __attribute__ ((__packed__)) hisdshdr32 {
|
||||||
uint32_t len; /* used */
|
uint32_t len; /* used */
|
||||||
uint32_t alloc; /* excluding the header and null terminator */
|
uint32_t alloc; /* excluding the header and null terminator */
|
||||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||||
@ -76,7 +78,7 @@ struct __attribute__ ((__packed__)) sdshdr32 {
|
|||||||
char buf[];
|
char buf[];
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct __attribute__ ((__packed__)) sdshdr64 {
|
struct __attribute__ ((__packed__)) hisdshdr64 {
|
||||||
uint64_t len; /* used */
|
uint64_t len; /* used */
|
||||||
uint64_t alloc; /* excluding the header and null terminator */
|
uint64_t alloc; /* excluding the header and null terminator */
|
||||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||||
@ -85,203 +87,203 @@ struct __attribute__ ((__packed__)) sdshdr64 {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SDS_TYPE_5 0
|
#define HI_SDS_TYPE_5 0
|
||||||
#define SDS_TYPE_8 1
|
#define HI_SDS_TYPE_8 1
|
||||||
#define SDS_TYPE_16 2
|
#define HI_SDS_TYPE_16 2
|
||||||
#define SDS_TYPE_32 3
|
#define HI_SDS_TYPE_32 3
|
||||||
#define SDS_TYPE_64 4
|
#define HI_SDS_TYPE_64 4
|
||||||
#define SDS_TYPE_MASK 7
|
#define HI_SDS_TYPE_MASK 7
|
||||||
#define SDS_TYPE_BITS 3
|
#define HI_SDS_TYPE_BITS 3
|
||||||
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
|
#define HI_SDS_HDR_VAR(T,s) struct hisdshdr##T *sh = (struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T)));
|
||||||
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
|
#define HI_SDS_HDR(T,s) ((struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T))))
|
||||||
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
|
#define HI_SDS_TYPE_5_LEN(f) ((f)>>HI_SDS_TYPE_BITS)
|
||||||
|
|
||||||
static inline size_t sdslen(const sds s) {
|
static inline size_t hi_sdslen(const hisds s) {
|
||||||
unsigned char flags = s[-1];
|
unsigned char flags = s[-1];
|
||||||
|
|
||||||
switch(__builtin_expect((flags&SDS_TYPE_MASK), SDS_TYPE_5)) {
|
switch(__builtin_expect((flags&HI_SDS_TYPE_MASK), HI_SDS_TYPE_5)) {
|
||||||
case SDS_TYPE_5:
|
case HI_SDS_TYPE_5:
|
||||||
return SDS_TYPE_5_LEN(flags);
|
return HI_SDS_TYPE_5_LEN(flags);
|
||||||
case SDS_TYPE_8:
|
case HI_SDS_TYPE_8:
|
||||||
return SDS_HDR(8,s)->len;
|
return HI_SDS_HDR(8,s)->len;
|
||||||
case SDS_TYPE_16:
|
case HI_SDS_TYPE_16:
|
||||||
return SDS_HDR(16,s)->len;
|
return HI_SDS_HDR(16,s)->len;
|
||||||
case SDS_TYPE_32:
|
case HI_SDS_TYPE_32:
|
||||||
return SDS_HDR(32,s)->len;
|
return HI_SDS_HDR(32,s)->len;
|
||||||
case SDS_TYPE_64:
|
case HI_SDS_TYPE_64:
|
||||||
return SDS_HDR(64,s)->len;
|
return HI_SDS_HDR(64,s)->len;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline size_t sdsavail(const sds s) {
|
static inline size_t hi_sdsavail(const hisds s) {
|
||||||
unsigned char flags = s[-1];
|
unsigned char flags = s[-1];
|
||||||
switch(flags&SDS_TYPE_MASK) {
|
switch(flags&HI_SDS_TYPE_MASK) {
|
||||||
case SDS_TYPE_5: {
|
case HI_SDS_TYPE_5: {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
case SDS_TYPE_8: {
|
case HI_SDS_TYPE_8: {
|
||||||
SDS_HDR_VAR(8,s);
|
HI_SDS_HDR_VAR(8,s);
|
||||||
return sh->alloc - sh->len;
|
return sh->alloc - sh->len;
|
||||||
}
|
}
|
||||||
case SDS_TYPE_16: {
|
case HI_SDS_TYPE_16: {
|
||||||
SDS_HDR_VAR(16,s);
|
HI_SDS_HDR_VAR(16,s);
|
||||||
return sh->alloc - sh->len;
|
return sh->alloc - sh->len;
|
||||||
}
|
}
|
||||||
case SDS_TYPE_32: {
|
case HI_SDS_TYPE_32: {
|
||||||
SDS_HDR_VAR(32,s);
|
HI_SDS_HDR_VAR(32,s);
|
||||||
return sh->alloc - sh->len;
|
return sh->alloc - sh->len;
|
||||||
}
|
}
|
||||||
case SDS_TYPE_64: {
|
case HI_SDS_TYPE_64: {
|
||||||
SDS_HDR_VAR(64,s);
|
HI_SDS_HDR_VAR(64,s);
|
||||||
return sh->alloc - sh->len;
|
return sh->alloc - sh->len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void sdssetlen(sds s, size_t newlen) {
|
static inline void hi_sdssetlen(hisds s, size_t newlen) {
|
||||||
unsigned char flags = s[-1];
|
unsigned char flags = s[-1];
|
||||||
switch(flags&SDS_TYPE_MASK) {
|
switch(flags&HI_SDS_TYPE_MASK) {
|
||||||
case SDS_TYPE_5:
|
case HI_SDS_TYPE_5:
|
||||||
{
|
{
|
||||||
unsigned char *fp = ((unsigned char*)s)-1;
|
unsigned char *fp = ((unsigned char*)s)-1;
|
||||||
*fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
|
*fp = (unsigned char)(HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_8:
|
case HI_SDS_TYPE_8:
|
||||||
SDS_HDR(8,s)->len = (uint8_t)newlen;
|
HI_SDS_HDR(8,s)->len = (uint8_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_16:
|
case HI_SDS_TYPE_16:
|
||||||
SDS_HDR(16,s)->len = (uint16_t)newlen;
|
HI_SDS_HDR(16,s)->len = (uint16_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_32:
|
case HI_SDS_TYPE_32:
|
||||||
SDS_HDR(32,s)->len = (uint32_t)newlen;
|
HI_SDS_HDR(32,s)->len = (uint32_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_64:
|
case HI_SDS_TYPE_64:
|
||||||
SDS_HDR(64,s)->len = (uint64_t)newlen;
|
HI_SDS_HDR(64,s)->len = (uint64_t)newlen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void sdsinclen(sds s, size_t inc) {
|
static inline void hi_sdsinclen(hisds s, size_t inc) {
|
||||||
unsigned char flags = s[-1];
|
unsigned char flags = s[-1];
|
||||||
switch(flags&SDS_TYPE_MASK) {
|
switch(flags&HI_SDS_TYPE_MASK) {
|
||||||
case SDS_TYPE_5:
|
case HI_SDS_TYPE_5:
|
||||||
{
|
{
|
||||||
unsigned char *fp = ((unsigned char*)s)-1;
|
unsigned char *fp = ((unsigned char*)s)-1;
|
||||||
unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
|
unsigned char newlen = HI_SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
|
||||||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
*fp = HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_8:
|
case HI_SDS_TYPE_8:
|
||||||
SDS_HDR(8,s)->len += (uint8_t)inc;
|
HI_SDS_HDR(8,s)->len += (uint8_t)inc;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_16:
|
case HI_SDS_TYPE_16:
|
||||||
SDS_HDR(16,s)->len += (uint16_t)inc;
|
HI_SDS_HDR(16,s)->len += (uint16_t)inc;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_32:
|
case HI_SDS_TYPE_32:
|
||||||
SDS_HDR(32,s)->len += (uint32_t)inc;
|
HI_SDS_HDR(32,s)->len += (uint32_t)inc;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_64:
|
case HI_SDS_TYPE_64:
|
||||||
SDS_HDR(64,s)->len += (uint64_t)inc;
|
HI_SDS_HDR(64,s)->len += (uint64_t)inc;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sdsalloc() = sdsavail() + sdslen() */
|
/* hi_sdsalloc() = hi_sdsavail() + hi_sdslen() */
|
||||||
static inline size_t sdsalloc(const sds s) {
|
static inline size_t hi_sdsalloc(const hisds s) {
|
||||||
unsigned char flags = s[-1];
|
unsigned char flags = s[-1];
|
||||||
switch(flags&SDS_TYPE_MASK) {
|
switch(flags & HI_SDS_TYPE_MASK) {
|
||||||
case SDS_TYPE_5:
|
case HI_SDS_TYPE_5:
|
||||||
return SDS_TYPE_5_LEN(flags);
|
return HI_SDS_TYPE_5_LEN(flags);
|
||||||
case SDS_TYPE_8:
|
case HI_SDS_TYPE_8:
|
||||||
return SDS_HDR(8,s)->alloc;
|
return HI_SDS_HDR(8,s)->alloc;
|
||||||
case SDS_TYPE_16:
|
case HI_SDS_TYPE_16:
|
||||||
return SDS_HDR(16,s)->alloc;
|
return HI_SDS_HDR(16,s)->alloc;
|
||||||
case SDS_TYPE_32:
|
case HI_SDS_TYPE_32:
|
||||||
return SDS_HDR(32,s)->alloc;
|
return HI_SDS_HDR(32,s)->alloc;
|
||||||
case SDS_TYPE_64:
|
case HI_SDS_TYPE_64:
|
||||||
return SDS_HDR(64,s)->alloc;
|
return HI_SDS_HDR(64,s)->alloc;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void sdssetalloc(sds s, size_t newlen) {
|
static inline void hi_sdssetalloc(hisds s, size_t newlen) {
|
||||||
unsigned char flags = s[-1];
|
unsigned char flags = s[-1];
|
||||||
switch(flags&SDS_TYPE_MASK) {
|
switch(flags&HI_SDS_TYPE_MASK) {
|
||||||
case SDS_TYPE_5:
|
case HI_SDS_TYPE_5:
|
||||||
/* Nothing to do, this type has no total allocation info. */
|
/* Nothing to do, this type has no total allocation info. */
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_8:
|
case HI_SDS_TYPE_8:
|
||||||
SDS_HDR(8,s)->alloc = (uint8_t)newlen;
|
HI_SDS_HDR(8,s)->alloc = (uint8_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_16:
|
case HI_SDS_TYPE_16:
|
||||||
SDS_HDR(16,s)->alloc = (uint16_t)newlen;
|
HI_SDS_HDR(16,s)->alloc = (uint16_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_32:
|
case HI_SDS_TYPE_32:
|
||||||
SDS_HDR(32,s)->alloc = (uint32_t)newlen;
|
HI_SDS_HDR(32,s)->alloc = (uint32_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_64:
|
case HI_SDS_TYPE_64:
|
||||||
SDS_HDR(64,s)->alloc = (uint64_t)newlen;
|
HI_SDS_HDR(64,s)->alloc = (uint64_t)newlen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sds sdsnewlen(const void *init, size_t initlen);
|
hisds hi_sdsnewlen(const void *init, size_t initlen);
|
||||||
sds sdsnew(const char *init);
|
hisds hi_sdsnew(const char *init);
|
||||||
sds sdsempty(void);
|
hisds hi_sdsempty(void);
|
||||||
sds sdsdup(const sds s);
|
hisds hi_sdsdup(const hisds s);
|
||||||
void sdsfree(sds s);
|
void hi_sdsfree(hisds s);
|
||||||
sds sdsgrowzero(sds s, size_t len);
|
hisds hi_sdsgrowzero(hisds s, size_t len);
|
||||||
sds sdscatlen(sds s, const void *t, size_t len);
|
hisds hi_sdscatlen(hisds s, const void *t, size_t len);
|
||||||
sds sdscat(sds s, const char *t);
|
hisds hi_sdscat(hisds s, const char *t);
|
||||||
sds sdscatsds(sds s, const sds t);
|
hisds hi_sdscatsds(hisds s, const hisds t);
|
||||||
sds sdscpylen(sds s, const char *t, size_t len);
|
hisds hi_sdscpylen(hisds s, const char *t, size_t len);
|
||||||
sds sdscpy(sds s, const char *t);
|
hisds hi_sdscpy(hisds s, const char *t);
|
||||||
|
|
||||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
|
hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap);
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
sds sdscatprintf(sds s, const char *fmt, ...)
|
hisds hi_sdscatprintf(hisds s, const char *fmt, ...)
|
||||||
__attribute__((format(printf, 2, 3)));
|
__attribute__((format(printf, 2, 3)));
|
||||||
#else
|
#else
|
||||||
sds sdscatprintf(sds s, const char *fmt, ...);
|
hisds hi_sdscatprintf(hisds s, const char *fmt, ...);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
sds sdscatfmt(sds s, char const *fmt, ...);
|
hisds hi_sdscatfmt(hisds s, char const *fmt, ...);
|
||||||
sds sdstrim(sds s, const char *cset);
|
hisds hi_sdstrim(hisds s, const char *cset);
|
||||||
void sdsrange(sds s, int start, int end);
|
int hi_sdsrange(hisds s, ssize_t start, ssize_t end);
|
||||||
void sdsupdatelen(sds s);
|
void hi_sdsupdatelen(hisds s);
|
||||||
void sdsclear(sds s);
|
void hi_sdsclear(hisds s);
|
||||||
int sdscmp(const sds s1, const sds s2);
|
int hi_sdscmp(const hisds s1, const hisds s2);
|
||||||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
|
hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
|
||||||
void sdsfreesplitres(sds *tokens, int count);
|
void hi_sdsfreesplitres(hisds *tokens, int count);
|
||||||
void sdstolower(sds s);
|
void hi_sdstolower(hisds s);
|
||||||
void sdstoupper(sds s);
|
void hi_sdstoupper(hisds s);
|
||||||
sds sdsfromlonglong(long long value);
|
hisds hi_sdsfromlonglong(long long value);
|
||||||
sds sdscatrepr(sds s, const char *p, size_t len);
|
hisds hi_sdscatrepr(hisds s, const char *p, size_t len);
|
||||||
sds *sdssplitargs(const char *line, int *argc);
|
hisds *hi_sdssplitargs(const char *line, int *argc);
|
||||||
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
|
hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen);
|
||||||
sds sdsjoin(char **argv, int argc, char *sep);
|
hisds hi_sdsjoin(char **argv, int argc, char *sep);
|
||||||
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
|
hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen);
|
||||||
|
|
||||||
/* Low level functions exposed to the user API */
|
/* Low level functions exposed to the user API */
|
||||||
sds sdsMakeRoomFor(sds s, size_t addlen);
|
hisds hi_sdsMakeRoomFor(hisds s, size_t addlen);
|
||||||
void sdsIncrLen(sds s, int incr);
|
void hi_sdsIncrLen(hisds s, int incr);
|
||||||
sds sdsRemoveFreeSpace(sds s);
|
hisds hi_sdsRemoveFreeSpace(hisds s);
|
||||||
size_t sdsAllocSize(sds s);
|
size_t hi_sdsAllocSize(hisds s);
|
||||||
void *sdsAllocPtr(sds s);
|
void *hi_sdsAllocPtr(hisds s);
|
||||||
|
|
||||||
/* Export the allocator used by SDS to the program using SDS.
|
/* Export the allocator used by SDS to the program using SDS.
|
||||||
* Sometimes the program SDS is linked to, may use a different set of
|
* 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
|
* allocators, but may want to allocate or free things that SDS will
|
||||||
* respectively free or allocate. */
|
* respectively free or allocate. */
|
||||||
void *sds_malloc(size_t size);
|
void *hi_sds_malloc(size_t size);
|
||||||
void *sds_realloc(void *ptr, size_t size);
|
void *hi_sds_realloc(void *ptr, size_t size);
|
||||||
void sds_free(void *ptr);
|
void hi_sds_free(void *ptr);
|
||||||
|
|
||||||
#ifdef REDIS_TEST
|
#ifdef REDIS_TEST
|
||||||
int sdsTest(int argc, char *argv[]);
|
int hi_sdsTest(int argc, char *argv[]);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif /* HIREDIS_SDS_H */
|
||||||
|
8
deps/hiredis/sdsalloc.h
vendored
8
deps/hiredis/sdsalloc.h
vendored
@ -37,6 +37,8 @@
|
|||||||
* the include of your alternate allocator if needed (not needed in order
|
* the include of your alternate allocator if needed (not needed in order
|
||||||
* to use the default libc allocator). */
|
* to use the default libc allocator). */
|
||||||
|
|
||||||
#define s_malloc malloc
|
#include "alloc.h"
|
||||||
#define s_realloc realloc
|
|
||||||
#define s_free free
|
#define hi_s_malloc hi_malloc
|
||||||
|
#define hi_s_realloc hi_realloc
|
||||||
|
#define hi_s_free hi_free
|
||||||
|
94
deps/hiredis/sdscompat.h
vendored
Normal file
94
deps/hiredis/sdscompat.h
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 */
|
2
deps/hiredis/sockcompat.c
vendored
2
deps/hiredis/sockcompat.c
vendored
@ -212,7 +212,7 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle
|
|||||||
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
|
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||||
struct timeval *tv = optval;
|
const struct timeval *tv = optval;
|
||||||
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
|
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
|
||||||
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
|
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
|
||||||
} else {
|
} else {
|
||||||
|
3
deps/hiredis/sockcompat.h
vendored
3
deps/hiredis/sockcompat.h
vendored
@ -49,9 +49,10 @@
|
|||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
typedef signed long ssize_t;
|
typedef long long ssize_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
|
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
|
||||||
|
318
deps/hiredis/ssl.c
vendored
318
deps/hiredis/ssl.c
vendored
@ -34,25 +34,33 @@
|
|||||||
#include "async.h"
|
#include "async.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <pthread.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
#include "win32.h"
|
||||||
#include "async_private.h"
|
#include "async_private.h"
|
||||||
|
#include "hiredis_ssl.h"
|
||||||
|
|
||||||
void __redisSetError(redisContext *c, int type, const char *str);
|
void __redisSetError(redisContext *c, int type, const char *str);
|
||||||
|
|
||||||
/* The SSL context is attached to SSL/TLS connections as a privdata. */
|
struct redisSSLContext {
|
||||||
typedef struct redisSSLContext {
|
/* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
|
||||||
/**
|
|
||||||
* OpenSSL SSL_CTX; It is optional and will not be set when using
|
|
||||||
* user-supplied SSL.
|
|
||||||
*/
|
|
||||||
SSL_CTX *ssl_ctx;
|
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.
|
* OpenSSL SSL object.
|
||||||
*/
|
*/
|
||||||
@ -72,43 +80,11 @@ typedef struct redisSSLContext {
|
|||||||
* should resume whenever a read takes place, if possible
|
* should resume whenever a read takes place, if possible
|
||||||
*/
|
*/
|
||||||
int pendingWrite;
|
int pendingWrite;
|
||||||
} redisSSLContext;
|
} redisSSL;
|
||||||
|
|
||||||
/* Forward declaration */
|
/* Forward declaration */
|
||||||
redisContextFuncs redisContextSSLFuncs;
|
redisContextFuncs redisContextSSLFuncs;
|
||||||
|
|
||||||
#ifdef HIREDIS_SSL_TRACE
|
|
||||||
/**
|
|
||||||
* Callback used for debugging
|
|
||||||
*/
|
|
||||||
static void sslLogCallback(const SSL *ssl, int where, int ret) {
|
|
||||||
const char *retstr = "";
|
|
||||||
int should_log = 1;
|
|
||||||
/* Ignore low-level SSL stuff */
|
|
||||||
|
|
||||||
if (where & SSL_CB_ALERT) {
|
|
||||||
should_log = 1;
|
|
||||||
}
|
|
||||||
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
|
|
||||||
should_log = 1;
|
|
||||||
}
|
|
||||||
if ((where & SSL_CB_EXIT) && ret == 0) {
|
|
||||||
should_log = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!should_log) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
retstr = SSL_alert_type_string(ret);
|
|
||||||
printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
|
|
||||||
|
|
||||||
if (where == SSL_CB_HANDSHAKE_DONE) {
|
|
||||||
printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenSSL global initialization and locking handling callbacks.
|
* OpenSSL global initialization and locking handling callbacks.
|
||||||
* Note that this is only required for OpenSSL < 1.1.0.
|
* Note that this is only required for OpenSSL < 1.1.0.
|
||||||
@ -119,6 +95,18 @@ static void sslLogCallback(const SSL *ssl, int where, int ret) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
#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;
|
typedef pthread_mutex_t sslLockType;
|
||||||
static void sslLockInit(sslLockType *l) {
|
static void sslLockInit(sslLockType *l) {
|
||||||
pthread_mutex_init(l, NULL);
|
pthread_mutex_init(l, NULL);
|
||||||
@ -129,7 +117,9 @@ static void sslLockAcquire(sslLockType *l) {
|
|||||||
static void sslLockRelease(sslLockType *l) {
|
static void sslLockRelease(sslLockType *l) {
|
||||||
pthread_mutex_unlock(l);
|
pthread_mutex_unlock(l);
|
||||||
}
|
}
|
||||||
static pthread_mutex_t *ossl_locks;
|
#endif
|
||||||
|
|
||||||
|
static sslLockType* ossl_locks;
|
||||||
|
|
||||||
static void opensslDoLock(int mode, int lkid, const char *f, int line) {
|
static void opensslDoLock(int mode, int lkid, const char *f, int line) {
|
||||||
sslLockType *l = ossl_locks + lkid;
|
sslLockType *l = ossl_locks + lkid;
|
||||||
@ -144,36 +134,151 @@ static void opensslDoLock(int mode, int lkid, const char *f, int line) {
|
|||||||
(void)line;
|
(void)line;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void initOpensslLocks(void) {
|
static int initOpensslLocks(void) {
|
||||||
unsigned ii, nlocks;
|
unsigned ii, nlocks;
|
||||||
if (CRYPTO_get_locking_callback() != NULL) {
|
if (CRYPTO_get_locking_callback() != NULL) {
|
||||||
/* Someone already set the callback before us. Don't destroy it! */
|
/* Someone already set the callback before us. Don't destroy it! */
|
||||||
return;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
nlocks = CRYPTO_num_locks();
|
nlocks = CRYPTO_num_locks();
|
||||||
ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
|
ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
|
||||||
|
if (ossl_locks == NULL)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
for (ii = 0; ii < nlocks; ii++) {
|
for (ii = 0; ii < nlocks; ii++) {
|
||||||
sslLockInit(ossl_locks + ii);
|
sslLockInit(ossl_locks + ii);
|
||||||
}
|
}
|
||||||
CRYPTO_set_locking_callback(opensslDoLock);
|
CRYPTO_set_locking_callback(opensslDoLock);
|
||||||
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
#endif /* HIREDIS_USE_CRYPTO_LOCKS */
|
#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";
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
|
||||||
|
if (ctx == NULL)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||||
|
if (!ctx->ssl_ctx) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||||
|
SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, 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) {
|
||||||
|
if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
|
||||||
|
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_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:
|
||||||
|
redisFreeSSLContext(ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SSL Connection initialization.
|
* SSL Connection initialization.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
|
|
||||||
if (c->privdata) {
|
static int redisSSLConnect(redisContext *c, SSL *ssl) {
|
||||||
|
if (c->privctx) {
|
||||||
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
|
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
c->privdata = calloc(1, sizeof(redisSSLContext));
|
|
||||||
|
redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
|
||||||
|
if (rssl == NULL) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
c->funcs = &redisContextSSLFuncs;
|
c->funcs = &redisContextSSLFuncs;
|
||||||
redisSSLContext *rssl = c->privdata;
|
|
||||||
|
|
||||||
rssl->ssl_ctx = ssl_ctx;
|
|
||||||
rssl->ssl = ssl;
|
rssl->ssl = ssl;
|
||||||
|
|
||||||
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||||
@ -183,12 +288,14 @@ static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
|
|||||||
ERR_clear_error();
|
ERR_clear_error();
|
||||||
int rv = SSL_connect(rssl->ssl);
|
int rv = SSL_connect(rssl->ssl);
|
||||||
if (rv == 1) {
|
if (rv == 1) {
|
||||||
|
c->privctx = rssl;
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = SSL_get_error(rssl->ssl, rv);
|
rv = SSL_get_error(rssl->ssl, rv);
|
||||||
if (((c->flags & REDIS_BLOCK) == 0) &&
|
if (((c->flags & REDIS_BLOCK) == 0) &&
|
||||||
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
|
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
|
||||||
|
c->privctx = rssl;
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,83 +310,58 @@ static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
|
|||||||
}
|
}
|
||||||
__redisSetError(c, REDIS_ERR_IO, err);
|
__redisSetError(c, REDIS_ERR_IO, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hi_free(rssl);
|
||||||
return REDIS_ERR;
|
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) {
|
int redisInitiateSSL(redisContext *c, SSL *ssl) {
|
||||||
return redisSSLConnect(c, NULL, ssl);
|
return redisSSLConnect(c, ssl);
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisSecureConnection(redisContext *c, const char *capath,
|
/**
|
||||||
const char *certpath, const char *keypath, const char *servername) {
|
* A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
|
||||||
|
* manage their own SSL objects.
|
||||||
|
*/
|
||||||
|
|
||||||
SSL_CTX *ssl_ctx = NULL;
|
int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
|
||||||
SSL *ssl = NULL;
|
{
|
||||||
|
if (!c || !redis_ssl_ctx)
|
||||||
|
return REDIS_ERR;
|
||||||
|
|
||||||
/* Initialize global OpenSSL stuff */
|
/* We want to verify that redisSSLConnect() won't fail on this, as it will
|
||||||
static int isInit = 0;
|
* not own the SSL object in that case and we'll end up leaking.
|
||||||
if (!isInit) {
|
*/
|
||||||
isInit = 1;
|
if (c->privctx)
|
||||||
SSL_library_init();
|
return REDIS_ERR;
|
||||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
|
||||||
initOpensslLocks();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
|
||||||
if (!ssl_ctx) {
|
|
||||||
__redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HIREDIS_SSL_TRACE
|
|
||||||
SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
|
|
||||||
#endif
|
|
||||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
|
||||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
|
||||||
if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
|
|
||||||
__redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (capath) {
|
|
||||||
if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
|
|
||||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (certpath) {
|
|
||||||
if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
|
|
||||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
|
|
||||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ssl = SSL_new(ssl_ctx);
|
|
||||||
if (!ssl) {
|
if (!ssl) {
|
||||||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
|
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
if (servername) {
|
|
||||||
if (!SSL_set_tlsext_host_name(ssl, servername)) {
|
if (redis_ssl_ctx->server_name) {
|
||||||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
|
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;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return redisSSLConnect(c, ssl_ctx, ssl);
|
return redisSSLConnect(c, ssl);
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if (ssl) SSL_free(ssl);
|
if (ssl)
|
||||||
if (ssl_ctx) SSL_CTX_free(ssl_ctx);
|
SSL_free(ssl);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int maybeCheckWant(redisSSLContext *rssl, int rv) {
|
static int maybeCheckWant(redisSSL *rssl, int rv) {
|
||||||
/**
|
/**
|
||||||
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
|
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
|
||||||
* and true is returned. False is returned otherwise
|
* and true is returned. False is returned otherwise
|
||||||
@ -299,23 +381,19 @@ static int maybeCheckWant(redisSSLContext *rssl, int rv) {
|
|||||||
* Implementation of redisContextFuncs for SSL connections.
|
* Implementation of redisContextFuncs for SSL connections.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void redisSSLFreeContext(void *privdata){
|
static void redisSSLFree(void *privctx){
|
||||||
redisSSLContext *rsc = privdata;
|
redisSSL *rsc = privctx;
|
||||||
|
|
||||||
if (!rsc) return;
|
if (!rsc) return;
|
||||||
if (rsc->ssl) {
|
if (rsc->ssl) {
|
||||||
SSL_free(rsc->ssl);
|
SSL_free(rsc->ssl);
|
||||||
rsc->ssl = NULL;
|
rsc->ssl = NULL;
|
||||||
}
|
}
|
||||||
if (rsc->ssl_ctx) {
|
hi_free(rsc);
|
||||||
SSL_CTX_free(rsc->ssl_ctx);
|
|
||||||
rsc->ssl_ctx = NULL;
|
|
||||||
}
|
|
||||||
free(rsc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
||||||
redisSSLContext *rssl = c->privdata;
|
redisSSL *rssl = c->privctx;
|
||||||
|
|
||||||
int nread = SSL_read(rssl->ssl, buf, bufcap);
|
int nread = SSL_read(rssl->ssl, buf, bufcap);
|
||||||
if (nread > 0) {
|
if (nread > 0) {
|
||||||
@ -356,10 +434,10 @@ static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int redisSSLWrite(redisContext *c) {
|
static ssize_t redisSSLWrite(redisContext *c) {
|
||||||
redisSSLContext *rssl = c->privdata;
|
redisSSL *rssl = c->privctx;
|
||||||
|
|
||||||
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
|
size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf);
|
||||||
int rv = SSL_write(rssl->ssl, c->obuf, len);
|
int rv = SSL_write(rssl->ssl, c->obuf, len);
|
||||||
|
|
||||||
if (rv > 0) {
|
if (rv > 0) {
|
||||||
@ -380,7 +458,7 @@ static int redisSSLWrite(redisContext *c) {
|
|||||||
|
|
||||||
static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
||||||
int rv;
|
int rv;
|
||||||
redisSSLContext *rssl = ac->c.privdata;
|
redisSSL *rssl = ac->c.privctx;
|
||||||
redisContext *c = &ac->c;
|
redisContext *c = &ac->c;
|
||||||
|
|
||||||
rssl->wantRead = 0;
|
rssl->wantRead = 0;
|
||||||
@ -410,7 +488,7 @@ static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
|||||||
|
|
||||||
static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
||||||
int rv, done = 0;
|
int rv, done = 0;
|
||||||
redisSSLContext *rssl = ac->c.privdata;
|
redisSSL *rssl = ac->c.privctx;
|
||||||
redisContext *c = &ac->c;
|
redisContext *c = &ac->c;
|
||||||
|
|
||||||
rssl->pendingWrite = 0;
|
rssl->pendingWrite = 0;
|
||||||
@ -439,7 +517,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redisContextFuncs redisContextSSLFuncs = {
|
redisContextFuncs redisContextSSLFuncs = {
|
||||||
.free_privdata = redisSSLFreeContext,
|
.free_privctx = redisSSLFree,
|
||||||
.async_read = redisSSLAsyncRead,
|
.async_read = redisSSLAsyncRead,
|
||||||
.async_write = redisSSLAsyncWrite,
|
.async_write = redisSSLAsyncWrite,
|
||||||
.read = redisSSLRead,
|
.read = redisSSLRead,
|
||||||
|
553
deps/hiredis/test.c
vendored
553
deps/hiredis/test.c
vendored
@ -1,22 +1,24 @@
|
|||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
|
#include "sockcompat.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#ifndef _WIN32
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <netdb.h>
|
#endif
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
#include "hiredis.h"
|
#include "hiredis.h"
|
||||||
|
#include "async.h"
|
||||||
#ifdef HIREDIS_TEST_SSL
|
#ifdef HIREDIS_TEST_SSL
|
||||||
#include "hiredis_ssl.h"
|
#include "hiredis_ssl.h"
|
||||||
#endif
|
#endif
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
#include "win32.h"
|
||||||
|
|
||||||
enum connection_type {
|
enum connection_type {
|
||||||
CONN_TCP,
|
CONN_TCP,
|
||||||
@ -47,15 +49,35 @@ struct config {
|
|||||||
} ssl;
|
} ssl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct privdata {
|
||||||
|
int dtor_counter;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct pushCounters {
|
||||||
|
int nil;
|
||||||
|
int str;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef HIREDIS_TEST_SSL
|
||||||
|
redisSSLContext *_ssl_ctx = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* The following lines make up our testing "framework" :) */
|
/* The following lines make up our testing "framework" :) */
|
||||||
static int tests = 0, fails = 0;
|
static int tests = 0, fails = 0, skips = 0;
|
||||||
#define test(_s) { printf("#%02d ", ++tests); printf(_s); }
|
#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_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 long long usec(void) {
|
static long long usec(void) {
|
||||||
|
#ifndef _MSC_VER
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
gettimeofday(&tv,NULL);
|
gettimeofday(&tv,NULL);
|
||||||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
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()
|
/* The assert() calls below have side effects, so we need assert()
|
||||||
@ -65,6 +87,43 @@ static long long usec(void) {
|
|||||||
#define assert(e) (void)(e)
|
#define assert(e) (void)(e)
|
||||||
#endif
|
#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) {
|
static redisContext *select_database(redisContext *c) {
|
||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
|
|
||||||
@ -87,6 +146,26 @@ static redisContext *select_database(redisContext *c) {
|
|||||||
return c;
|
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) {
|
static int disconnect(redisContext *c, int keep_fd) {
|
||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
|
|
||||||
@ -105,9 +184,9 @@ static int disconnect(redisContext *c, int keep_fd) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_ssl_handshake(redisContext *c, struct config config) {
|
static void do_ssl_handshake(redisContext *c) {
|
||||||
#ifdef HIREDIS_TEST_SSL
|
#ifdef HIREDIS_TEST_SSL
|
||||||
redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL);
|
redisInitiateSSLWithContext(c, _ssl_ctx);
|
||||||
if (c->err) {
|
if (c->err) {
|
||||||
printf("SSL error: %s\n", c->errstr);
|
printf("SSL error: %s\n", c->errstr);
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
@ -115,7 +194,6 @@ static void do_ssl_handshake(redisContext *c, struct config config) {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
(void) c;
|
(void) c;
|
||||||
(void) config;
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +228,7 @@ static redisContext *do_connect(struct config config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.type == CONN_SSL) {
|
if (config.type == CONN_SSL) {
|
||||||
do_ssl_handshake(c, config);
|
do_ssl_handshake(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return select_database(c);
|
return select_database(c);
|
||||||
@ -160,7 +238,7 @@ static void do_reconnect(redisContext *c, struct config config) {
|
|||||||
redisReconnect(c);
|
redisReconnect(c);
|
||||||
|
|
||||||
if (config.type == CONN_SSL) {
|
if (config.type == CONN_SSL) {
|
||||||
do_ssl_handshake(c, config);
|
do_ssl_handshake(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,43 +250,43 @@ static void test_format_commands(void) {
|
|||||||
len = redisFormatCommand(&cmd,"SET foo bar");
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
test("Format command with %%s string interpolation: ");
|
test("Format command with %%s string interpolation: ");
|
||||||
len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
test("Format command with %%s and an empty string: ");
|
test("Format command with %%s and an empty string: ");
|
||||||
len = redisFormatCommand(&cmd,"SET %s %s","foo","");
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
test("Format command with an empty string in between proper interpolations: ");
|
test("Format command with an empty string in between proper interpolations: ");
|
||||||
len = redisFormatCommand(&cmd,"SET %s %s","","foo");
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(0+2)+4+(3+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
test("Format command with %%b string interpolation: ");
|
test("Format command with %%b string interpolation: ");
|
||||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
test("Format command with %%b and an empty string: ");
|
test("Format command with %%b and an empty string: ");
|
||||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
test("Format command with literal %%: ");
|
test("Format command with literal %%: ");
|
||||||
len = redisFormatCommand(&cmd,"SET %% %%");
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(1+2)+4+(1+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
/* Vararg width depends on the type. These tests make sure that the
|
/* Vararg width depends on the type. These tests make sure that the
|
||||||
* width is correctly determined using the format and subsequent varargs
|
* width is correctly determined using the format and subsequent varargs
|
||||||
@ -219,7 +297,7 @@ static void test_format_commands(void) {
|
|||||||
len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
|
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 && \
|
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)); \
|
len == 4+5+(12+2)+4+(9+2)); \
|
||||||
free(cmd); \
|
hi_free(cmd); \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
#define FLOAT_WIDTH_TEST(type) do { \
|
#define FLOAT_WIDTH_TEST(type) do { \
|
||||||
@ -228,7 +306,7 @@ static void test_format_commands(void) {
|
|||||||
len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \
|
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 && \
|
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)); \
|
len == 4+5+(12+2)+4+(9+2)); \
|
||||||
free(cmd); \
|
hi_free(cmd); \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
INTEGER_WIDTH_TEST("d", int);
|
INTEGER_WIDTH_TEST("d", int);
|
||||||
@ -259,29 +337,29 @@ static void test_format_commands(void) {
|
|||||||
len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
test("Format command by passing argc/argv with lengths: ");
|
test("Format command by passing argc/argv with lengths: ");
|
||||||
len = redisFormatCommandArgv(&cmd,argc,argv,lens);
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
|
|
||||||
sds sds_cmd;
|
hisds sds_cmd;
|
||||||
|
|
||||||
sds_cmd = sdsempty();
|
sds_cmd = NULL;
|
||||||
test("Format command into sds by passing argc/argv without lengths: ");
|
test("Format command into hisds by passing argc/argv without lengths: ");
|
||||||
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||||
sdsfree(sds_cmd);
|
hi_sdsfree(sds_cmd);
|
||||||
|
|
||||||
sds_cmd = sdsempty();
|
sds_cmd = NULL;
|
||||||
test("Format command into sds by passing argc/argv with lengths: ");
|
test("Format command into hisds by passing argc/argv with lengths: ");
|
||||||
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
|
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 &&
|
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));
|
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||||
sdsfree(sds_cmd);
|
hi_sdsfree(sds_cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_append_formatted_commands(struct config config) {
|
static void test_append_formatted_commands(struct config config) {
|
||||||
@ -300,7 +378,7 @@ static void test_append_formatted_commands(struct config config) {
|
|||||||
|
|
||||||
assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
|
assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
|
||||||
|
|
||||||
free(cmd);
|
hi_free(cmd);
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
|
|
||||||
disconnect(c, 0);
|
disconnect(c, 0);
|
||||||
@ -308,7 +386,7 @@ static void test_append_formatted_commands(struct config config) {
|
|||||||
|
|
||||||
static void test_reply_reader(void) {
|
static void test_reply_reader(void) {
|
||||||
redisReader *reader;
|
redisReader *reader;
|
||||||
void *reply;
|
void *reply, *root;
|
||||||
int ret;
|
int ret;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@ -332,16 +410,26 @@ static void test_reply_reader(void) {
|
|||||||
strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
|
strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
|
||||||
redisReaderFree(reader);
|
redisReaderFree(reader);
|
||||||
|
|
||||||
test("Set error on nested multi bulks with depth > 7: ");
|
|
||||||
reader = redisReaderCreate();
|
reader = redisReaderCreate();
|
||||||
|
test("Can handle arbitrarily nested multi-bulks: ");
|
||||||
for (i = 0; i < 9; i++) {
|
for (i = 0; i < 128; i++) {
|
||||||
redisReaderFeed(reader,(char*)"*1\r\n", 4);
|
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);
|
||||||
|
|
||||||
ret = redisReaderGetReply(reader,NULL);
|
test("Can parse arbitrarily nested multi-bulks correctly: ");
|
||||||
test_cond(ret == REDIS_ERR &&
|
while(i--) {
|
||||||
strncasecmp(reader->errstr,"No support for",14) == 0);
|
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);
|
redisReaderFree(reader);
|
||||||
|
|
||||||
test("Correctly parses LLONG_MAX: ");
|
test("Correctly parses LLONG_MAX: ");
|
||||||
@ -400,6 +488,16 @@ static void test_reply_reader(void) {
|
|||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
redisReaderFree(reader);
|
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);
|
||||||
|
|
||||||
#if LLONG_MAX > SIZE_MAX
|
#if LLONG_MAX > SIZE_MAX
|
||||||
test("Set error when array > SIZE_MAX: ");
|
test("Set error when array > SIZE_MAX: ");
|
||||||
reader = redisReaderCreate();
|
reader = redisReaderCreate();
|
||||||
@ -459,6 +557,32 @@ static void test_reply_reader(void) {
|
|||||||
((redisReply*)reply)->elements == 0);
|
((redisReply*)reply)->elements == 0);
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
redisReaderFree(reader);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_free_null(void) {
|
static void test_free_null(void) {
|
||||||
@ -474,6 +598,47 @@ static void test_free_null(void) {
|
|||||||
test_cond(reply == NULL);
|
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_realloc_fail(void *ptr, size_t size) {
|
||||||
|
(void)ptr;
|
||||||
|
(void)size;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_allocator_injection(void) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Return allocators to default
|
||||||
|
hiredisResetAllocators();
|
||||||
|
}
|
||||||
|
|
||||||
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
|
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
|
||||||
static void test_blocking_connection_errors(void) {
|
static void test_blocking_connection_errors(void) {
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
@ -491,19 +656,19 @@ static void test_blocking_connection_errors(void) {
|
|||||||
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
||||||
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
|
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
|
||||||
strcmp(c->errstr, "Name does not resolve") == 0 ||
|
strcmp(c->errstr, "Name does not resolve") == 0 ||
|
||||||
strcmp(c->errstr,
|
strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
|
||||||
"nodename nor servname provided, or not known") == 0 ||
|
|
||||||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
||||||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
|
strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
|
||||||
strcmp(c->errstr,
|
strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
|
||||||
"hostname nor servname provided, or not known") == 0 ||
|
strcmp(c->errstr, "no address associated with name") == 0 ||
|
||||||
strcmp(c->errstr, "no address associated with name") == 0));
|
strcmp(c->errstr, "No such host is known. ") == 0));
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
} else {
|
} else {
|
||||||
printf("Skipping NXDOMAIN test. Found evil ISP!\n");
|
printf("Skipping NXDOMAIN test. Found evil ISP!\n");
|
||||||
freeaddrinfo(ai_tmp);
|
freeaddrinfo(ai_tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
test("Returns error when the port is not open: ");
|
test("Returns error when the port is not open: ");
|
||||||
c = redisConnect((char*)"localhost", 1);
|
c = redisConnect((char*)"localhost", 1);
|
||||||
test_cond(c->err == REDIS_ERR_IO &&
|
test_cond(c->err == REDIS_ERR_IO &&
|
||||||
@ -514,11 +679,166 @@ static void test_blocking_connection_errors(void) {
|
|||||||
c = redisConnectUnix((char*)"/tmp/idontexist.sock");
|
c = redisConnectUnix((char*)"/tmp/idontexist.sock");
|
||||||
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
|
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
|
||||||
redisFree(c);
|
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");
|
||||||
|
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");
|
||||||
|
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);
|
||||||
|
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) {
|
static void test_blocking_connection(struct config config) {
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
|
int major;
|
||||||
|
|
||||||
c = do_connect(config);
|
c = do_connect(config);
|
||||||
|
|
||||||
@ -591,14 +911,42 @@ static void test_blocking_connection(struct config config) {
|
|||||||
strcasecmp(reply->element[1]->str,"pong") == 0);
|
strcasecmp(reply->element[1]->str,"pong") == 0);
|
||||||
freeReplyObject(reply);
|
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);
|
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) {
|
static void test_blocking_connection_timeouts(struct config config) {
|
||||||
redisContext *c;
|
redisContext *c;
|
||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
ssize_t s;
|
ssize_t s;
|
||||||
const char *cmd = "DEBUG SLEEP 3\r\n";
|
const char *sleep_cmd = "DEBUG SLEEP 3\r\n";
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
|
||||||
c = do_connect(config);
|
c = do_connect(config);
|
||||||
@ -615,14 +963,24 @@ static void test_blocking_connection_timeouts(struct config config) {
|
|||||||
|
|
||||||
c = do_connect(config);
|
c = do_connect(config);
|
||||||
test("Does not return a reply when the command times out: ");
|
test("Does not return a reply when the command times out: ");
|
||||||
redisAppendFormattedCommand(c, cmd, strlen(cmd));
|
if (detect_debug_sleep(c)) {
|
||||||
|
redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
|
||||||
s = c->funcs->write(c);
|
s = c->funcs->write(c);
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = 0;
|
||||||
tv.tv_usec = 10000;
|
tv.tv_usec = 10000;
|
||||||
redisSetTimeout(c, tv);
|
redisSetTimeout(c, tv);
|
||||||
reply = redisCommand(c, "GET foo");
|
reply = redisCommand(c, "GET foo");
|
||||||
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0);
|
#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);
|
freeReplyObject(reply);
|
||||||
|
} else {
|
||||||
|
test_skipped();
|
||||||
|
}
|
||||||
|
|
||||||
test("Reconnect properly reconnects after a timeout: ");
|
test("Reconnect properly reconnects after a timeout: ");
|
||||||
do_reconnect(c, config);
|
do_reconnect(c, config);
|
||||||
@ -649,18 +1007,7 @@ static void test_blocking_io_errors(struct config config) {
|
|||||||
|
|
||||||
/* Connect to target given by config. */
|
/* Connect to target given by config. */
|
||||||
c = do_connect(config);
|
c = do_connect(config);
|
||||||
{
|
get_redis_version(c, &major, &minor);
|
||||||
/* Find out Redis version to determine the path for the next test */
|
|
||||||
const char *field = "redis_version:";
|
|
||||||
char *p, *eptr;
|
|
||||||
|
|
||||||
reply = redisCommand(c,"INFO");
|
|
||||||
p = strstr(reply->str,field);
|
|
||||||
major = strtol(p+strlen(field),&eptr,10);
|
|
||||||
p = eptr+1; /* char next to the first "." */
|
|
||||||
minor = strtol(p,&eptr,10);
|
|
||||||
freeReplyObject(reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
test("Returns I/O error when the connection is lost: ");
|
test("Returns I/O error when the connection is lost: ");
|
||||||
reply = redisCommand(c,"QUIT");
|
reply = redisCommand(c,"QUIT");
|
||||||
@ -674,6 +1021,7 @@ static void test_blocking_io_errors(struct config config) {
|
|||||||
test_cond(reply == NULL);
|
test_cond(reply == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
/* On 2.0, QUIT will cause the connection to be closed immediately and
|
/* 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.
|
* 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
|
* On >2.0, QUIT will return with OK and another read(2) needed to be
|
||||||
@ -681,14 +1029,19 @@ static void test_blocking_io_errors(struct config config) {
|
|||||||
* conditions, the error will be set to EOF. */
|
* conditions, the error will be set to EOF. */
|
||||||
assert(c->err == REDIS_ERR_EOF &&
|
assert(c->err == REDIS_ERR_EOF &&
|
||||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||||
|
#endif
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
|
|
||||||
c = do_connect(config);
|
c = do_connect(config);
|
||||||
test("Returns I/O error on socket timeout: ");
|
test("Returns I/O error on socket timeout: ");
|
||||||
struct timeval tv = { 0, 1000 };
|
struct timeval tv = { 0, 1000 };
|
||||||
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
||||||
test_cond(redisGetReply(c,&_reply) == REDIS_ERR &&
|
int respcode = redisGetReply(c,&_reply);
|
||||||
c->err == REDIS_ERR_IO && errno == EAGAIN);
|
#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);
|
redisFree(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -716,6 +1069,18 @@ static void test_invalid_timeout_errors(struct config config) {
|
|||||||
redisFree(c);
|
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) {
|
static void test_throughput(struct config config) {
|
||||||
redisContext *c = do_connect(config);
|
redisContext *c = do_connect(config);
|
||||||
redisReply **replies;
|
redisReply **replies;
|
||||||
@ -727,7 +1092,7 @@ static void test_throughput(struct config config) {
|
|||||||
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
||||||
|
|
||||||
num = 1000;
|
num = 1000;
|
||||||
replies = malloc(sizeof(redisReply*)*num);
|
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||||
t1 = usec();
|
t1 = usec();
|
||||||
for (i = 0; i < num; i++) {
|
for (i = 0; i < num; i++) {
|
||||||
replies[i] = redisCommand(c,"PING");
|
replies[i] = redisCommand(c,"PING");
|
||||||
@ -735,10 +1100,10 @@ static void test_throughput(struct config config) {
|
|||||||
}
|
}
|
||||||
t2 = usec();
|
t2 = usec();
|
||||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||||
free(replies);
|
hi_free(replies);
|
||||||
printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
|
printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||||
|
|
||||||
replies = malloc(sizeof(redisReply*)*num);
|
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||||
t1 = usec();
|
t1 = usec();
|
||||||
for (i = 0; i < num; i++) {
|
for (i = 0; i < num; i++) {
|
||||||
replies[i] = redisCommand(c,"LRANGE mylist 0 499");
|
replies[i] = redisCommand(c,"LRANGE mylist 0 499");
|
||||||
@ -747,10 +1112,10 @@ static void test_throughput(struct config config) {
|
|||||||
}
|
}
|
||||||
t2 = usec();
|
t2 = usec();
|
||||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||||
free(replies);
|
hi_free(replies);
|
||||||
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
|
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||||
|
|
||||||
replies = malloc(sizeof(redisReply*)*num);
|
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||||
t1 = usec();
|
t1 = usec();
|
||||||
for (i = 0; i < num; i++) {
|
for (i = 0; i < num; i++) {
|
||||||
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
|
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
|
||||||
@ -758,11 +1123,11 @@ static void test_throughput(struct config config) {
|
|||||||
}
|
}
|
||||||
t2 = usec();
|
t2 = usec();
|
||||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||||
free(replies);
|
hi_free(replies);
|
||||||
printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
|
printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||||
|
|
||||||
num = 10000;
|
num = 10000;
|
||||||
replies = malloc(sizeof(redisReply*)*num);
|
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||||
for (i = 0; i < num; i++)
|
for (i = 0; i < num; i++)
|
||||||
redisAppendCommand(c,"PING");
|
redisAppendCommand(c,"PING");
|
||||||
t1 = usec();
|
t1 = usec();
|
||||||
@ -772,10 +1137,10 @@ static void test_throughput(struct config config) {
|
|||||||
}
|
}
|
||||||
t2 = usec();
|
t2 = usec();
|
||||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||||
free(replies);
|
hi_free(replies);
|
||||||
printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||||
|
|
||||||
replies = malloc(sizeof(redisReply*)*num);
|
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||||
for (i = 0; i < num; i++)
|
for (i = 0; i < num; i++)
|
||||||
redisAppendCommand(c,"LRANGE mylist 0 499");
|
redisAppendCommand(c,"LRANGE mylist 0 499");
|
||||||
t1 = usec();
|
t1 = usec();
|
||||||
@ -786,10 +1151,10 @@ static void test_throughput(struct config config) {
|
|||||||
}
|
}
|
||||||
t2 = usec();
|
t2 = usec();
|
||||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||||
free(replies);
|
hi_free(replies);
|
||||||
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||||
|
|
||||||
replies = malloc(sizeof(redisReply*)*num);
|
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||||
for (i = 0; i < num; i++)
|
for (i = 0; i < num; i++)
|
||||||
redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
|
redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
|
||||||
t1 = usec();
|
t1 = usec();
|
||||||
@ -799,7 +1164,7 @@ static void test_throughput(struct config config) {
|
|||||||
}
|
}
|
||||||
t2 = usec();
|
t2 = usec();
|
||||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||||
free(replies);
|
hi_free(replies);
|
||||||
printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||||
|
|
||||||
disconnect(c, 0);
|
disconnect(c, 0);
|
||||||
@ -916,9 +1281,8 @@ int main(int argc, char **argv) {
|
|||||||
};
|
};
|
||||||
int throughput = 1;
|
int throughput = 1;
|
||||||
int test_inherit_fd = 1;
|
int test_inherit_fd = 1;
|
||||||
|
int skips_as_fails = 0;
|
||||||
/* Ignore broken pipe signal (for I/O error tests). */
|
int test_unix_socket;
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
|
|
||||||
/* Parse command line options. */
|
/* Parse command line options. */
|
||||||
argv++; argc--;
|
argv++; argc--;
|
||||||
@ -936,6 +1300,8 @@ int main(int argc, char **argv) {
|
|||||||
throughput = 0;
|
throughput = 0;
|
||||||
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
||||||
test_inherit_fd = 0;
|
test_inherit_fd = 0;
|
||||||
|
} else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) {
|
||||||
|
skips_as_fails = 1;
|
||||||
#ifdef HIREDIS_TEST_SSL
|
#ifdef HIREDIS_TEST_SSL
|
||||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
|
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
|
||||||
argv++; argc--;
|
argv++; argc--;
|
||||||
@ -960,6 +1326,19 @@ int main(int argc, char **argv) {
|
|||||||
argv++; argc--;
|
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_format_commands();
|
||||||
test_reply_reader();
|
test_reply_reader();
|
||||||
test_blocking_connection_errors();
|
test_blocking_connection_errors();
|
||||||
@ -974,15 +1353,25 @@ int main(int argc, char **argv) {
|
|||||||
test_append_formatted_commands(cfg);
|
test_append_formatted_commands(cfg);
|
||||||
if (throughput) test_throughput(cfg);
|
if (throughput) test_throughput(cfg);
|
||||||
|
|
||||||
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path);
|
printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
|
||||||
|
if (test_unix_socket) {
|
||||||
|
printf("\n");
|
||||||
cfg.type = CONN_UNIX;
|
cfg.type = CONN_UNIX;
|
||||||
test_blocking_connection(cfg);
|
test_blocking_connection(cfg);
|
||||||
test_blocking_connection_timeouts(cfg);
|
test_blocking_connection_timeouts(cfg);
|
||||||
test_blocking_io_errors(cfg);
|
test_blocking_io_errors(cfg);
|
||||||
if (throughput) test_throughput(cfg);
|
if (throughput) test_throughput(cfg);
|
||||||
|
} else {
|
||||||
|
test_skipped();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HIREDIS_TEST_SSL
|
#ifdef HIREDIS_TEST_SSL
|
||||||
if (cfg.ssl.port && cfg.ssl.host) {
|
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);
|
printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
|
||||||
cfg.type = CONN_SSL;
|
cfg.type = CONN_SSL;
|
||||||
|
|
||||||
@ -992,21 +1381,31 @@ int main(int argc, char **argv) {
|
|||||||
test_invalid_timeout_errors(cfg);
|
test_invalid_timeout_errors(cfg);
|
||||||
test_append_formatted_commands(cfg);
|
test_append_formatted_commands(cfg);
|
||||||
if (throughput) test_throughput(cfg);
|
if (throughput) test_throughput(cfg);
|
||||||
|
|
||||||
|
redisFreeSSLContext(_ssl_ctx);
|
||||||
|
_ssl_ctx = NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (test_inherit_fd) {
|
if (test_inherit_fd) {
|
||||||
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
|
printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
|
||||||
|
if (test_unix_socket) {
|
||||||
|
printf("\n");
|
||||||
cfg.type = CONN_FD;
|
cfg.type = CONN_FD;
|
||||||
test_blocking_connection(cfg);
|
test_blocking_connection(cfg);
|
||||||
|
} else {
|
||||||
|
test_skipped();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fails || (skips_as_fails && skips)) {
|
||||||
if (fails) {
|
|
||||||
printf("*** %d TESTS FAILED ***\n", fails);
|
printf("*** %d TESTS FAILED ***\n", fails);
|
||||||
|
if (skips) {
|
||||||
|
printf("*** %d TESTS SKIPPED ***\n", skips);
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("ALL TESTS PASSED\n");
|
printf("ALL TESTS PASSED (%d skipped)\n", skips);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
10
deps/hiredis/test.sh
vendored
10
deps/hiredis/test.sh
vendored
@ -4,7 +4,9 @@ REDIS_SERVER=${REDIS_SERVER:-redis-server}
|
|||||||
REDIS_PORT=${REDIS_PORT:-56379}
|
REDIS_PORT=${REDIS_PORT:-56379}
|
||||||
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
||||||
TEST_SSL=${TEST_SSL:-0}
|
TEST_SSL=${TEST_SSL:-0}
|
||||||
|
SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0}
|
||||||
SSL_TEST_ARGS=
|
SSL_TEST_ARGS=
|
||||||
|
SKIPS_ARG=
|
||||||
|
|
||||||
tmpdir=$(mktemp -d)
|
tmpdir=$(mktemp -d)
|
||||||
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
||||||
@ -67,4 +69,10 @@ fi
|
|||||||
cat ${tmpdir}/redis.conf
|
cat ${tmpdir}/redis.conf
|
||||||
${REDIS_SERVER} ${tmpdir}/redis.conf
|
${REDIS_SERVER} ${tmpdir}/redis.conf
|
||||||
|
|
||||||
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS}
|
# Wait until we detect the unix socket
|
||||||
|
while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
|
||||||
|
|
||||||
|
# Treat skips as failures if directed
|
||||||
|
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="--skips-as-fails"
|
||||||
|
|
||||||
|
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}
|
||||||
|
2
deps/jemalloc/configure.ac
vendored
2
deps/jemalloc/configure.ac
vendored
@ -517,7 +517,7 @@ CTARGET='-o $@'
|
|||||||
LDTARGET='-o $@'
|
LDTARGET='-o $@'
|
||||||
TEST_LD_MODE=
|
TEST_LD_MODE=
|
||||||
EXTRA_LDFLAGS=
|
EXTRA_LDFLAGS=
|
||||||
ARFLAGS='crus'
|
ARFLAGS='crs'
|
||||||
AROUT=' $@'
|
AROUT=' $@'
|
||||||
CC_MM=1
|
CC_MM=1
|
||||||
|
|
||||||
|
2
deps/lua/src/ldo.c
vendored
2
deps/lua/src/ldo.c
vendored
@ -493,7 +493,7 @@ static void f_parser (lua_State *L, void *ud) {
|
|||||||
Proto *tf;
|
Proto *tf;
|
||||||
Closure *cl;
|
Closure *cl;
|
||||||
struct SParser *p = cast(struct SParser *, ud);
|
struct SParser *p = cast(struct SParser *, ud);
|
||||||
int c = luaZ_lookahead(p->z);
|
luaZ_lookahead(p->z);
|
||||||
luaC_checkGC(L);
|
luaC_checkGC(L);
|
||||||
tf = (luaY_parser)(L, p->z,
|
tf = (luaY_parser)(L, p->z,
|
||||||
&p->buff, p->name);
|
&p->buff, p->name);
|
||||||
|
File diff suppressed because it is too large
Load Diff
2
runtest
2
runtest
@ -2,6 +2,8 @@
|
|||||||
TCL_VERSIONS="8.5 8.6"
|
TCL_VERSIONS="8.5 8.6"
|
||||||
TCLSH=""
|
TCLSH=""
|
||||||
|
|
||||||
|
export ASAN_OPTIONS=allocator_may_return_null=1 $ASAN_OPTIONS
|
||||||
|
|
||||||
for VERSION in $TCL_VERSIONS; do
|
for VERSION in $TCL_VERSIONS; do
|
||||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||||
done
|
done
|
||||||
|
@ -8,7 +8,7 @@ done
|
|||||||
|
|
||||||
if [ -z $TCLSH ]
|
if [ -z $TCLSH ]
|
||||||
then
|
then
|
||||||
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test"
|
echo "You need tcl 8.5 or newer in order to run the Redis Cluster test"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
$TCLSH tests/cluster/run.tcl $*
|
$TCLSH tests/cluster/run.tcl $*
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
TCL_VERSIONS="8.5 8.6"
|
TCL_VERSIONS="8.5 8.6"
|
||||||
TCLSH=""
|
TCLSH=""
|
||||||
|
[ -z "$MAKE" ] && MAKE=make
|
||||||
|
|
||||||
for VERSION in $TCL_VERSIONS; do
|
for VERSION in $TCL_VERSIONS; do
|
||||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||||
@ -8,11 +9,11 @@ done
|
|||||||
|
|
||||||
if [ -z $TCLSH ]
|
if [ -z $TCLSH ]
|
||||||
then
|
then
|
||||||
echo "You need tcl 8.5 or newer in order to run the Redis test"
|
echo "You need tcl 8.5 or newer in order to run the Redis ModuleApi test"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
make -C tests/modules && \
|
$MAKE -C tests/modules && \
|
||||||
$TCLSH tests/test_helper.tcl \
|
$TCLSH tests/test_helper.tcl \
|
||||||
--single unit/moduleapi/commandfilter \
|
--single unit/moduleapi/commandfilter \
|
||||||
--single unit/moduleapi/fork \
|
--single unit/moduleapi/fork \
|
||||||
@ -22,6 +23,7 @@ $TCLSH tests/test_helper.tcl \
|
|||||||
--single unit/moduleapi/hooks \
|
--single unit/moduleapi/hooks \
|
||||||
--single unit/moduleapi/misc \
|
--single unit/moduleapi/misc \
|
||||||
--single unit/moduleapi/blockonkeys \
|
--single unit/moduleapi/blockonkeys \
|
||||||
|
--single unit/moduleapi/blockonbackground \
|
||||||
--single unit/moduleapi/scan \
|
--single unit/moduleapi/scan \
|
||||||
--single unit/moduleapi/datatype \
|
--single unit/moduleapi/datatype \
|
||||||
--single unit/moduleapi/auth \
|
--single unit/moduleapi/auth \
|
||||||
@ -30,5 +32,10 @@ $TCLSH tests/test_helper.tcl \
|
|||||||
--single unit/moduleapi/moduleloadsave \
|
--single unit/moduleapi/moduleloadsave \
|
||||||
--single unit/moduleapi/getkeys \
|
--single unit/moduleapi/getkeys \
|
||||||
--single unit/moduleapi/timers \
|
--single unit/moduleapi/timers \
|
||||||
|
--single unit/moduleapi/test_lazyfree \
|
||||||
|
--single unit/moduleapi/defrag \
|
||||||
|
--single unit/moduleapi/hash \
|
||||||
|
--single unit/moduleapi/zset \
|
||||||
|
--single unit/moduleapi/stream \
|
||||||
--config server-threads 3 \
|
--config server-threads 3 \
|
||||||
"${@}"
|
"${@}"
|
||||||
|
@ -124,6 +124,42 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
|||||||
# Default is 30 seconds.
|
# Default is 30 seconds.
|
||||||
sentinel down-after-milliseconds mymaster 30000
|
sentinel down-after-milliseconds mymaster 30000
|
||||||
|
|
||||||
|
# IMPORTANT NOTE: starting with KeyDB 6.2 ACL capability is supported for
|
||||||
|
# Sentinel mode, please refer to the KeyDB website https://redis.io/topics/acl
|
||||||
|
# for more details.
|
||||||
|
|
||||||
|
# Sentinel's ACL users are defined in the following format:
|
||||||
|
#
|
||||||
|
# user <username> ... 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/topics/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 <password>
|
# requirepass <password>
|
||||||
#
|
#
|
||||||
# You can configure Sentinel itself to require a password, however when doing
|
# You can configure Sentinel itself to require a password, however when doing
|
||||||
@ -131,6 +167,29 @@ sentinel down-after-milliseconds mymaster 30000
|
|||||||
# other Sentinels. So you need to configure all your Sentinels in a given
|
# other Sentinels. So you need to configure all your Sentinels in a given
|
||||||
# group with the same "requirepass" password. Check the following documentation
|
# group with the same "requirepass" password. Check the following documentation
|
||||||
# for more info: https://redis.io/topics/sentinel
|
# for more info: https://redis.io/topics/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 <password> as usually, or more explicitly with AUTH default <password>
|
||||||
|
# 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 compatable with aclfile option and the ACL LOAD
|
||||||
|
# command, these will cause requirepass to be ignored.
|
||||||
|
|
||||||
|
# sentinel sentinel-user <username>
|
||||||
|
#
|
||||||
|
# You can configure Sentinel to authenticate with other Sentinels with specific
|
||||||
|
# user name.
|
||||||
|
|
||||||
|
# sentinel sentinel-pass <password>
|
||||||
|
#
|
||||||
|
# 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 <master-name> <numreplicas>
|
# sentinel parallel-syncs <master-name> <numreplicas>
|
||||||
#
|
#
|
||||||
@ -262,3 +321,21 @@ sentinel deny-scripts-reconfig yes
|
|||||||
# is possible to just rename a command to itself:
|
# is possible to just rename a command to itself:
|
||||||
#
|
#
|
||||||
# SENTINEL rename-command mymaster CONFIG CONFIG
|
# 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
|
||||||
|
74
src/Makefile
74
src/Makefile
@ -16,13 +16,15 @@ release_hdr := $(shell sh -c './mkreleasehdr.sh')
|
|||||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||||
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
|
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
|
||||||
OPTIMIZATION?=-O2
|
OPTIMIZATION?=-O2
|
||||||
DEPENDENCY_TARGETS=hiredis linenoise lua
|
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram
|
||||||
NODEPS:=clean distclean
|
NODEPS:=clean distclean
|
||||||
|
|
||||||
# Default settings
|
# Default settings
|
||||||
STD=-std=c11 -pedantic -DREDIS_STATIC=''
|
STD=-pedantic -DREDIS_STATIC=''
|
||||||
CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS
|
CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS
|
||||||
ifneq (,$(findstring clang,$(CC)))
|
ifneq (,$(findstring clang,$(CC)))
|
||||||
|
STD+=-Wno-c11-extensions
|
||||||
|
else
|
||||||
ifneq (,$(findstring FreeBSD,$(uname_S)))
|
ifneq (,$(findstring FreeBSD,$(uname_S)))
|
||||||
STD+=-Wno-c11-extensions
|
STD+=-Wno-c11-extensions
|
||||||
endif
|
endif
|
||||||
@ -30,6 +32,16 @@ endif
|
|||||||
WARN=-Wall -W -Wno-missing-field-initializers
|
WARN=-Wall -W -Wno-missing-field-initializers
|
||||||
OPT=$(OPTIMIZATION)
|
OPT=$(OPTIMIZATION)
|
||||||
|
|
||||||
|
# Detect if the compiler supports C11 _Atomic
|
||||||
|
C11_ATOMIC := $(shell sh -c 'echo "\#include <stdatomic.h>" > foo.c; \
|
||||||
|
$(CC) -std=c11 -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=c11
|
||||||
|
else
|
||||||
|
STD+=-std=c99
|
||||||
|
endif
|
||||||
|
|
||||||
PREFIX?=/usr/local
|
PREFIX?=/usr/local
|
||||||
INSTALL_BIN=$(PREFIX)/bin
|
INSTALL_BIN=$(PREFIX)/bin
|
||||||
INSTALL=install
|
INSTALL=install
|
||||||
@ -117,9 +129,11 @@ endif
|
|||||||
|
|
||||||
ifeq ($(uname_S),SunOS)
|
ifeq ($(uname_S),SunOS)
|
||||||
# SunOS
|
# SunOS
|
||||||
ifneq ($(@@),32bit)
|
ifeq ($(findstring -m32,$(FINAL_CFLAGS)),)
|
||||||
CFLAGS+=-m64
|
CFLAGS+=-m64
|
||||||
CXXFLAGS+= -m64
|
CXXFLAGS+= -m64
|
||||||
|
endif
|
||||||
|
ifeq ($(findstring -m32,$(FINAL_LDFLAGS)),)
|
||||||
LDFLAGS+=-m64
|
LDFLAGS+=-m64
|
||||||
endif
|
endif
|
||||||
DEBUG=-g
|
DEBUG=-g
|
||||||
@ -133,8 +147,18 @@ else
|
|||||||
ifeq ($(uname_S),Darwin)
|
ifeq ($(uname_S),Darwin)
|
||||||
# Darwin
|
# Darwin
|
||||||
FINAL_LIBS+= -ldl
|
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_CFLAGS=-I/opt/homebrew/opt/openssl/include
|
||||||
|
OPENSSL_LDFLAGS=-L/opt/homebrew/opt/openssl/lib
|
||||||
|
else
|
||||||
|
# Homebrew x86/ppc uses /usr/local as HOMEBREW_PREFIX
|
||||||
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
||||||
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||||
|
endif
|
||||||
else
|
else
|
||||||
ifeq ($(uname_S),AIX)
|
ifeq ($(uname_S),AIX)
|
||||||
# AIX
|
# AIX
|
||||||
@ -202,11 +226,13 @@ endif
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
# Include paths to dependencies
|
# Include paths to dependencies
|
||||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram
|
||||||
FINAL_CXXFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
FINAL_CXXFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram
|
||||||
|
|
||||||
# Determine systemd support and/or build preference (defaulting to auto-detection)
|
# Determine systemd support and/or build preference (defaulting to auto-detection)
|
||||||
BUILD_WITH_SYSTEMD=no
|
BUILD_WITH_SYSTEMD=no
|
||||||
|
LIBSYSTEMD_LIBS=-lsystemd
|
||||||
|
|
||||||
# If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to
|
# If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to
|
||||||
# auto-detect libsystemd's presence and link accordingly.
|
# auto-detect libsystemd's presence and link accordingly.
|
||||||
ifneq ($(USE_SYSTEMD),no)
|
ifneq ($(USE_SYSTEMD),no)
|
||||||
@ -215,17 +241,18 @@ ifneq ($(USE_SYSTEMD),no)
|
|||||||
# (unless a later check tells us otherwise)
|
# (unless a later check tells us otherwise)
|
||||||
ifeq ($(LIBSYSTEMD_PKGCONFIG),0)
|
ifeq ($(LIBSYSTEMD_PKGCONFIG),0)
|
||||||
BUILD_WITH_SYSTEMD=yes
|
BUILD_WITH_SYSTEMD=yes
|
||||||
|
LIBSYSTEMD_LIBS=$(shell $(PKG_CONFIG) --libs libsystemd)
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# If 'USE_SYSTEMD' is set to "yes" use pkg-config if available or fall back to
|
||||||
|
# default -lsystemd.
|
||||||
ifeq ($(USE_SYSTEMD),yes)
|
ifeq ($(USE_SYSTEMD),yes)
|
||||||
ifneq ($(LIBSYSTEMD_PKGCONFIG),0)
|
|
||||||
$(error USE_SYSTEMD is set to "$(USE_SYSTEMD)", but $(PKG_CONFIG) cannot find libsystemd)
|
|
||||||
endif
|
|
||||||
# Force building with libsystemd
|
|
||||||
BUILD_WITH_SYSTEMD=yes
|
BUILD_WITH_SYSTEMD=yes
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(BUILD_WITH_SYSTEMD),yes)
|
ifeq ($(BUILD_WITH_SYSTEMD),yes)
|
||||||
FINAL_LIBS+=$(shell $(PKG_CONFIG) --libs libsystemd)
|
FINAL_LIBS+=$(LIBSYSTEMD_LIBS)
|
||||||
FINAL_CFLAGS+= -DHAVE_LIBSYSTEMD
|
FINAL_CFLAGS+= -DHAVE_LIBSYSTEMD
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@ -256,10 +283,9 @@ ifeq ($(MALLOC),memkind)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(BUILD_TLS),yes)
|
ifeq ($(BUILD_TLS),yes)
|
||||||
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
|
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
|
||||||
FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
|
FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
|
||||||
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
||||||
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
|
|
||||||
LIBSSL_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libssl && echo $$?)
|
LIBSSL_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libssl && echo $$?)
|
||||||
ifeq ($(LIBSSL_PKGCONFIG),0)
|
ifeq ($(LIBSSL_PKGCONFIG),0)
|
||||||
LIBSSL_LIBS=$(shell $(PKG_CONFIG) --libs libssl)
|
LIBSSL_LIBS=$(shell $(PKG_CONFIG) --libs libssl)
|
||||||
@ -296,11 +322,11 @@ endif
|
|||||||
|
|
||||||
REDIS_SERVER_NAME=keydb-server$(PROG_SUFFIX)
|
REDIS_SERVER_NAME=keydb-server$(PROG_SUFFIX)
|
||||||
REDIS_SENTINEL_NAME=keydb-sentinel$(PROG_SUFFIX)
|
REDIS_SENTINEL_NAME=keydb-sentinel$(PROG_SUFFIX)
|
||||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_nhash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.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 acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(ASM_OBJ)
|
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_nhash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.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 acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o $(ASM_OBJ)
|
||||||
REDIS_CLI_NAME=keydb-cli$(PROG_SUFFIX)
|
REDIS_CLI_NAME=keydb-cli$(PROG_SUFFIX)
|
||||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
|
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o monotonic.o cli_common.o mt19937-64.o $(ASM_OBJ)
|
||||||
REDIS_BENCHMARK_NAME=keydb-benchmark$(PROG_SUFFIX)
|
REDIS_BENCHMARK_NAME=keydb-benchmark$(PROG_SUFFIX)
|
||||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ)
|
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o release.o crcspeed.o crc64.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o monotonic.o cli_common.o mt19937-64.o $(ASM_OBJ)
|
||||||
REDIS_CHECK_RDB_NAME=keydb-check-rdb$(PROG_SUFFIX)
|
REDIS_CHECK_RDB_NAME=keydb-check-rdb$(PROG_SUFFIX)
|
||||||
REDIS_CHECK_AOF_NAME=keydb-check-aof$(PROG_SUFFIX)
|
REDIS_CHECK_AOF_NAME=keydb-check-aof$(PROG_SUFFIX)
|
||||||
|
|
||||||
@ -374,9 +400,9 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
|||||||
|
|
||||||
# keydb-benchmark
|
# keydb-benchmark
|
||||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
|
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)
|
||||||
|
|
||||||
dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c
|
dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c mt19937-64.c
|
||||||
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
||||||
|
|
||||||
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
||||||
@ -407,10 +433,10 @@ distclean: clean
|
|||||||
|
|
||||||
.PHONY: distclean
|
.PHONY: distclean
|
||||||
|
|
||||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
|
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME)
|
||||||
@(cd ..; ./runtest)
|
@(cd ..; ./runtest)
|
||||||
|
|
||||||
test-sentinel: $(REDIS_SENTINEL_NAME)
|
test-sentinel: $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME)
|
||||||
@(cd ..; ./runtest-sentinel)
|
@(cd ..; ./runtest-sentinel)
|
||||||
|
|
||||||
check: test
|
check: test
|
||||||
@ -422,10 +448,6 @@ lcov:
|
|||||||
@genhtml --legend -o lcov-html KeyDB.info
|
@genhtml --legend -o lcov-html KeyDB.info
|
||||||
@genhtml --legend -o lcov-html KeyDB.info | grep lines | awk '{print $$2;}' | sed 's/%//g'
|
@genhtml --legend -o lcov-html KeyDB.info | grep lines | awk '{print $$2;}' | sed 's/%//g'
|
||||||
|
|
||||||
test-sds: sds.c sds.h
|
|
||||||
$(REDIS_CC) sds.c zmalloc.cpp -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test
|
|
||||||
/tmp/sds_test
|
|
||||||
|
|
||||||
.PHONY: lcov
|
.PHONY: lcov
|
||||||
|
|
||||||
bench: $(REDIS_BENCHMARK_NAME)
|
bench: $(REDIS_BENCHMARK_NAME)
|
||||||
@ -447,7 +469,7 @@ valgrind:
|
|||||||
$(MAKE) OPTIMIZATION="-O0" USEASM="false" MALLOC="libc" CFLAGS="-DSANITIZE" CXXFLAGS="-DSANITIZE"
|
$(MAKE) OPTIMIZATION="-O0" USEASM="false" MALLOC="libc" CFLAGS="-DSANITIZE" CXXFLAGS="-DSANITIZE"
|
||||||
|
|
||||||
helgrind:
|
helgrind:
|
||||||
$(MAKE) OPTIMIZATION="-O0" MALLOC="libc" CFLAGS="-D__ATOMIC_VAR_FORCE_SYNC_MACROS"
|
$(MAKE) OPTIMIZATION="-O0" MALLOC="libc" CFLAGS="-D__ATOMIC_VAR_FORCE_SYNC_MACROS" REDIS_CFLAGS="-I/usr/local/include" REDIS_LDFLAGS="-L/usr/local/lib"
|
||||||
|
|
||||||
src/help.h:
|
src/help.h:
|
||||||
@../utils/generate-command-help.rb > help.h
|
@../utils/generate-command-help.rb > help.h
|
||||||
@ -457,8 +479,8 @@ install: all
|
|||||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
|
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
|
||||||
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
|
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
|
||||||
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
||||||
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
|
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME)
|
||||||
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
|
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME)
|
||||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
|
369
src/acl.cpp
369
src/acl.cpp
@ -55,6 +55,10 @@ list *UsersToLoad; /* This is a list of users found in the configuration file
|
|||||||
list *ACLLog; /* Our security log, the user is able to inspect that
|
list *ACLLog; /* Our security log, the user is able to inspect that
|
||||||
using the ACL LOG command .*/
|
using the ACL LOG command .*/
|
||||||
|
|
||||||
|
static rax *commandId = NULL; /* Command name to id mapping */
|
||||||
|
|
||||||
|
static unsigned long nextid = 0; /* Next command id that has not been assigned */
|
||||||
|
|
||||||
struct ACLCategoryItem {
|
struct ACLCategoryItem {
|
||||||
const char *name;
|
const char *name;
|
||||||
uint64_t flag;
|
uint64_t flag;
|
||||||
@ -88,18 +92,22 @@ struct ACLUserFlag {
|
|||||||
const char *name;
|
const char *name;
|
||||||
uint64_t flag;
|
uint64_t flag;
|
||||||
} ACLUserFlags[] = {
|
} ACLUserFlags[] = {
|
||||||
|
/* Note: the order here dictates the emitted order at ACLDescribeUser */
|
||||||
{"on", USER_FLAG_ENABLED},
|
{"on", USER_FLAG_ENABLED},
|
||||||
{"off", USER_FLAG_DISABLED},
|
{"off", USER_FLAG_DISABLED},
|
||||||
{"allkeys", USER_FLAG_ALLKEYS},
|
{"allkeys", USER_FLAG_ALLKEYS},
|
||||||
|
{"allchannels", USER_FLAG_ALLCHANNELS},
|
||||||
{"allcommands", USER_FLAG_ALLCOMMANDS},
|
{"allcommands", USER_FLAG_ALLCOMMANDS},
|
||||||
{"nopass", USER_FLAG_NOPASS},
|
{"nopass", USER_FLAG_NOPASS},
|
||||||
|
{"skip-sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD_SKIP},
|
||||||
|
{"sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD},
|
||||||
{NULL,0} /* Terminator. */
|
{NULL,0} /* Terminator. */
|
||||||
};
|
};
|
||||||
|
|
||||||
void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
||||||
void ACLResetSubcommands(user *u);
|
void ACLResetSubcommands(user *u);
|
||||||
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
||||||
void ACLFreeLogEntry(struct ACLLogEntry *le);
|
void ACLFreeLogEntry(const void *le);
|
||||||
|
|
||||||
/* The length of the string representation of a hashed password. */
|
/* The length of the string representation of a hashed password. */
|
||||||
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
|
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
|
||||||
@ -240,16 +248,20 @@ user *ACLCreateUser(const char *name, size_t namelen) {
|
|||||||
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
|
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
|
||||||
user *u = (user*)zmalloc(sizeof(*u), MALLOC_LOCAL);
|
user *u = (user*)zmalloc(sizeof(*u), MALLOC_LOCAL);
|
||||||
u->name = sdsnewlen(name,namelen);
|
u->name = sdsnewlen(name,namelen);
|
||||||
u->flags = USER_FLAG_DISABLED;
|
u->flags = USER_FLAG_DISABLED | g_pserver->acl_pubusub_default;
|
||||||
u->allowed_subcommands = NULL;
|
u->allowed_subcommands = NULL;
|
||||||
u->passwords = listCreate();
|
u->passwords = listCreate();
|
||||||
u->patterns = listCreate();
|
u->patterns = listCreate();
|
||||||
|
u->channels = listCreate();
|
||||||
listSetMatchMethod(u->passwords,ACLListMatchSds);
|
listSetMatchMethod(u->passwords,ACLListMatchSds);
|
||||||
listSetFreeMethod(u->passwords,ACLListFreeSds);
|
listSetFreeMethod(u->passwords,ACLListFreeSds);
|
||||||
listSetDupMethod(u->passwords,ACLListDupSds);
|
listSetDupMethod(u->passwords,ACLListDupSds);
|
||||||
listSetMatchMethod(u->patterns,ACLListMatchSds);
|
listSetMatchMethod(u->patterns,ACLListMatchSds);
|
||||||
listSetFreeMethod(u->patterns,ACLListFreeSds);
|
listSetFreeMethod(u->patterns,ACLListFreeSds);
|
||||||
listSetDupMethod(u->patterns,ACLListDupSds);
|
listSetDupMethod(u->patterns,ACLListDupSds);
|
||||||
|
listSetMatchMethod(u->channels,ACLListMatchSds);
|
||||||
|
listSetFreeMethod(u->channels,ACLListFreeSds);
|
||||||
|
listSetDupMethod(u->channels,ACLListDupSds);
|
||||||
memset(u->allowed_commands,0,sizeof(u->allowed_commands));
|
memset(u->allowed_commands,0,sizeof(u->allowed_commands));
|
||||||
raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
|
raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
|
||||||
return u;
|
return u;
|
||||||
@ -278,6 +290,7 @@ void ACLFreeUser(user *u) {
|
|||||||
sdsfree(u->name);
|
sdsfree(u->name);
|
||||||
listRelease(u->passwords);
|
listRelease(u->passwords);
|
||||||
listRelease(u->patterns);
|
listRelease(u->patterns);
|
||||||
|
listRelease(u->channels);
|
||||||
ACLResetSubcommands(u);
|
ACLResetSubcommands(u);
|
||||||
zfree(u);
|
zfree(u);
|
||||||
}
|
}
|
||||||
@ -291,14 +304,14 @@ void ACLFreeUserAndKillClients(user *u) {
|
|||||||
listRewind(g_pserver->clients,&li);
|
listRewind(g_pserver->clients,&li);
|
||||||
while ((ln = listNext(&li)) != NULL) {
|
while ((ln = listNext(&li)) != NULL) {
|
||||||
client *c = (client*)listNodeValue(ln);
|
client *c = (client*)listNodeValue(ln);
|
||||||
if (c->puser == u) {
|
if (c->user == u) {
|
||||||
/* We'll free the conenction asynchronously, so
|
/* We'll free the connection asynchronously, so
|
||||||
* in theory to set a different user is not needed.
|
* in theory to set a different user is not needed.
|
||||||
* However if there are bugs in Redis, soon or later
|
* However if there are bugs in Redis, soon or later
|
||||||
* this may result in some security hole: it's much
|
* this may result in some security hole: it's much
|
||||||
* more defensive to set the default user and put
|
* more defensive to set the default user and put
|
||||||
* it in non authenticated mode. */
|
* it in non authenticated mode. */
|
||||||
c->puser = DefaultUser;
|
c->user = DefaultUser;
|
||||||
c->authenticated = 0;
|
c->authenticated = 0;
|
||||||
/* We will write replies to this client later, so we can't
|
/* We will write replies to this client later, so we can't
|
||||||
* close it directly even if async. */
|
* close it directly even if async. */
|
||||||
@ -318,8 +331,10 @@ void ACLFreeUserAndKillClients(user *u) {
|
|||||||
void ACLCopyUser(user *dst, user *src) {
|
void ACLCopyUser(user *dst, user *src) {
|
||||||
listRelease(dst->passwords);
|
listRelease(dst->passwords);
|
||||||
listRelease(dst->patterns);
|
listRelease(dst->patterns);
|
||||||
|
listRelease(dst->channels);
|
||||||
dst->passwords = listDup(src->passwords);
|
dst->passwords = listDup(src->passwords);
|
||||||
dst->patterns = listDup(src->patterns);
|
dst->patterns = listDup(src->patterns);
|
||||||
|
dst->channels = listDup(src->channels);
|
||||||
memcpy(dst->allowed_commands,src->allowed_commands,
|
memcpy(dst->allowed_commands,src->allowed_commands,
|
||||||
sizeof(dst->allowed_commands));
|
sizeof(dst->allowed_commands));
|
||||||
dst->flags = src->flags;
|
dst->flags = src->flags;
|
||||||
@ -601,9 +616,10 @@ sds ACLDescribeUser(user *u) {
|
|||||||
|
|
||||||
/* Flags. */
|
/* Flags. */
|
||||||
for (int j = 0; ACLUserFlags[j].flag; j++) {
|
for (int j = 0; ACLUserFlags[j].flag; j++) {
|
||||||
/* Skip the allcommands and allkeys flags because they'll be emitted
|
/* Skip the allcommands, allkeys and allchannels flags because they'll
|
||||||
* later as ~* and +@all. */
|
* be emitted later as +@all, ~* and &*. */
|
||||||
if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS ||
|
if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS ||
|
||||||
|
ACLUserFlags[j].flag == USER_FLAG_ALLCHANNELS ||
|
||||||
ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue;
|
ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue;
|
||||||
if (u->flags & ACLUserFlags[j].flag) {
|
if (u->flags & ACLUserFlags[j].flag) {
|
||||||
res = sdscat(res,ACLUserFlags[j].name);
|
res = sdscat(res,ACLUserFlags[j].name);
|
||||||
@ -635,6 +651,19 @@ sds ACLDescribeUser(user *u) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pub/sub channel patterns. */
|
||||||
|
if (u->flags & USER_FLAG_ALLCHANNELS) {
|
||||||
|
res = sdscatlen(res,"&* ",3);
|
||||||
|
} else {
|
||||||
|
listRewind(u->channels,&li);
|
||||||
|
while((ln = listNext(&li))) {
|
||||||
|
sds thispat = (sds)listNodeValue(ln);
|
||||||
|
res = sdscatlen(res,"&",1);
|
||||||
|
res = sdscatsds(res,thispat);
|
||||||
|
res = sdscatlen(res," ",1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Command rules. */
|
/* Command rules. */
|
||||||
sds rules = ACLDescribeUserCommandRules(u);
|
sds rules = ACLDescribeUserCommandRules(u);
|
||||||
res = sdscatsds(res,rules);
|
res = sdscatsds(res,rules);
|
||||||
@ -680,7 +709,6 @@ void ACLResetSubcommands(user *u) {
|
|||||||
u->allowed_subcommands = NULL;
|
u->allowed_subcommands = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Add a subcommand to the list of subcommands for the user 'u' and
|
/* Add a subcommand to the list of subcommands for the user 'u' and
|
||||||
* the command id specified. */
|
* the command id specified. */
|
||||||
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
||||||
@ -741,6 +769,12 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
|||||||
* It is possible to specify multiple patterns.
|
* It is possible to specify multiple patterns.
|
||||||
* allkeys Alias for ~*
|
* allkeys Alias for ~*
|
||||||
* resetkeys Flush the list of allowed keys patterns.
|
* resetkeys Flush the list of allowed keys patterns.
|
||||||
|
* &<pattern> 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 keys patterns.
|
||||||
* ><password> Add this password to the list of valid password for the user.
|
* ><password> Add this password to the list of valid password for the user.
|
||||||
* For example >mypass will add "mypass" to the list.
|
* For example >mypass will add "mypass" to the list.
|
||||||
* This directive clears the "nopass" flag (see later).
|
* This directive clears the "nopass" flag (see later).
|
||||||
@ -779,14 +813,14 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
|||||||
*
|
*
|
||||||
* When an error is returned, errno is set to the following values:
|
* When an error is returned, errno is set to the following values:
|
||||||
*
|
*
|
||||||
* EINVAL: The specified opcode is not understood or the key pattern is
|
* EINVAL: The specified opcode is not understood or the key/channel pattern is
|
||||||
* invalid (contains non allowed characters).
|
* invalid (contains non allowed characters).
|
||||||
* ENOENT: The command name or command category provided with + or - is not
|
* ENOENT: The command name or command category provided with + or - is not
|
||||||
* known.
|
* known.
|
||||||
* EBUSY: The subcommand you want to add is about a command that is currently
|
|
||||||
* fully added.
|
|
||||||
* EEXIST: You are adding a key pattern after "*" was already added. This is
|
* EEXIST: You are adding a key pattern after "*" was already added. This is
|
||||||
* almost surely an error on the user side.
|
* 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.
|
* 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.
|
* EBADMSG: The hash you are trying to add is not a valid hash.
|
||||||
*/
|
*/
|
||||||
@ -799,6 +833,12 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
} else if (!strcasecmp(op,"off")) {
|
} else if (!strcasecmp(op,"off")) {
|
||||||
u->flags |= USER_FLAG_DISABLED;
|
u->flags |= USER_FLAG_DISABLED;
|
||||||
u->flags &= ~USER_FLAG_ENABLED;
|
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,"allkeys") ||
|
} else if (!strcasecmp(op,"allkeys") ||
|
||||||
!strcasecmp(op,"~*"))
|
!strcasecmp(op,"~*"))
|
||||||
{
|
{
|
||||||
@ -807,6 +847,14 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
} else if (!strcasecmp(op,"resetkeys")) {
|
} else if (!strcasecmp(op,"resetkeys")) {
|
||||||
u->flags &= ~USER_FLAG_ALLKEYS;
|
u->flags &= ~USER_FLAG_ALLKEYS;
|
||||||
listEmpty(u->patterns);
|
listEmpty(u->patterns);
|
||||||
|
} else if (!strcasecmp(op,"allchannels") ||
|
||||||
|
!strcasecmp(op,"&*"))
|
||||||
|
{
|
||||||
|
u->flags |= USER_FLAG_ALLCHANNELS;
|
||||||
|
listEmpty(u->channels);
|
||||||
|
} else if (!strcasecmp(op,"resetchannels")) {
|
||||||
|
u->flags &= ~USER_FLAG_ALLCHANNELS;
|
||||||
|
listEmpty(u->channels);
|
||||||
} else if (!strcasecmp(op,"allcommands") ||
|
} else if (!strcasecmp(op,"allcommands") ||
|
||||||
!strcasecmp(op,"+@all"))
|
!strcasecmp(op,"+@all"))
|
||||||
{
|
{
|
||||||
@ -874,12 +922,29 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
}
|
}
|
||||||
sds newpat = sdsnewlen(op+1,oplen-1);
|
sds newpat = sdsnewlen(op+1,oplen-1);
|
||||||
listNode *ln = listSearchKey(u->patterns,newpat);
|
listNode *ln = listSearchKey(u->patterns,newpat);
|
||||||
/* Avoid re-adding the same pattern multiple times. */
|
/* Avoid re-adding the same key pattern multiple times. */
|
||||||
if (ln == NULL)
|
if (ln == NULL)
|
||||||
listAddNodeTail(u->patterns,newpat);
|
listAddNodeTail(u->patterns,newpat);
|
||||||
else
|
else
|
||||||
sdsfree(newpat);
|
sdsfree(newpat);
|
||||||
u->flags &= ~USER_FLAG_ALLKEYS;
|
u->flags &= ~USER_FLAG_ALLKEYS;
|
||||||
|
} else if (op[0] == '&') {
|
||||||
|
if (u->flags & USER_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(u->channels,newpat);
|
||||||
|
/* Avoid re-adding the same channel pattern multiple times. */
|
||||||
|
if (ln == NULL)
|
||||||
|
listAddNodeTail(u->channels,newpat);
|
||||||
|
else
|
||||||
|
sdsfree(newpat);
|
||||||
|
u->flags &= ~USER_FLAG_ALLCHANNELS;
|
||||||
} else if (op[0] == '+' && op[1] != '@') {
|
} else if (op[0] == '+' && op[1] != '@') {
|
||||||
if (strchr(op,'|') == NULL) {
|
if (strchr(op,'|') == NULL) {
|
||||||
if (ACLLookupCommand(op+1) == NULL) {
|
if (ACLLookupCommand(op+1) == NULL) {
|
||||||
@ -912,22 +977,12 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The command should not be set right now in the command
|
|
||||||
* bitmap, because adding a subcommand of a fully added
|
|
||||||
* command is probably an error on the user side. */
|
|
||||||
unsigned long id = ACLGetCommandID(copy);
|
unsigned long id = ACLGetCommandID(copy);
|
||||||
if (ACLGetUserCommandBit(u,id) == 1) {
|
/* Add the subcommand to the list of valid ones, if the command is not set. */
|
||||||
zfree(copy);
|
if (ACLGetUserCommandBit(u,id) == 0) {
|
||||||
errno = EBUSY;
|
ACLAddAllowedSubcommand(u,id,sub);
|
||||||
return C_ERR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add the subcommand to the list of valid ones. */
|
|
||||||
ACLAddAllowedSubcommand(u,id,sub);
|
|
||||||
|
|
||||||
/* We have to clear the command bit so that we force the
|
|
||||||
* subcommand check. */
|
|
||||||
ACLSetUserCommandBit(u,id,0);
|
|
||||||
zfree(copy);
|
zfree(copy);
|
||||||
}
|
}
|
||||||
} else if (op[0] == '-' && op[1] != '@') {
|
} else if (op[0] == '-' && op[1] != '@') {
|
||||||
@ -947,7 +1002,9 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
} else if (!strcasecmp(op,"reset")) {
|
} else if (!strcasecmp(op,"reset")) {
|
||||||
serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
|
serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
|
||||||
serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
|
serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
|
||||||
|
serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK);
|
||||||
serverAssert(ACLSetUser(u,"off",-1) == C_OK);
|
serverAssert(ACLSetUser(u,"off",-1) == C_OK);
|
||||||
|
serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK);
|
||||||
serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
|
serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
|
||||||
} else {
|
} else {
|
||||||
errno = EINVAL;
|
errno = EINVAL;
|
||||||
@ -964,15 +1021,16 @@ const char *ACLSetUserStringError(void) {
|
|||||||
errmsg = "Unknown command or category name in ACL";
|
errmsg = "Unknown command or category name in ACL";
|
||||||
else if (errno == EINVAL)
|
else if (errno == EINVAL)
|
||||||
errmsg = "Syntax error";
|
errmsg = "Syntax error";
|
||||||
else if (errno == EBUSY)
|
|
||||||
errmsg = "Adding a subcommand of a command already fully "
|
|
||||||
"added is not allowed. Remove the command to start. "
|
|
||||||
"Example: -DEBUG +DEBUG|DIGEST";
|
|
||||||
else if (errno == EEXIST)
|
else if (errno == EEXIST)
|
||||||
errmsg = "Adding a pattern after the * pattern (or the "
|
errmsg = "Adding a pattern after the * pattern (or the "
|
||||||
"'allkeys' flag) is not valid and does not have any "
|
"'allkeys' flag) is not valid and does not have any "
|
||||||
"effect. Try 'resetkeys' to start with an empty "
|
"effect. Try 'resetkeys' to start with an empty "
|
||||||
"list of patterns";
|
"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)
|
else if (errno == ENODEV)
|
||||||
errmsg = "The password you are trying to remove from the user does "
|
errmsg = "The password you are trying to remove from the user does "
|
||||||
"not exist";
|
"not exist";
|
||||||
@ -988,6 +1046,7 @@ void ACLInitDefaultUser(void) {
|
|||||||
DefaultUser = ACLCreateUser("default",7);
|
DefaultUser = ACLCreateUser("default",7);
|
||||||
ACLSetUser(DefaultUser,"+@all",-1);
|
ACLSetUser(DefaultUser,"+@all",-1);
|
||||||
ACLSetUser(DefaultUser,"~*",-1);
|
ACLSetUser(DefaultUser,"~*",-1);
|
||||||
|
ACLSetUser(DefaultUser,"&*",-1);
|
||||||
ACLSetUser(DefaultUser,"on",-1);
|
ACLSetUser(DefaultUser,"on",-1);
|
||||||
ACLSetUser(DefaultUser,"nopass",-1);
|
ACLSetUser(DefaultUser,"nopass",-1);
|
||||||
}
|
}
|
||||||
@ -998,7 +1057,6 @@ void ACLInit(void) {
|
|||||||
UsersToLoad = listCreate();
|
UsersToLoad = listCreate();
|
||||||
ACLLog = listCreate();
|
ACLLog = listCreate();
|
||||||
ACLInitDefaultUser();
|
ACLInitDefaultUser();
|
||||||
g_pserver->requirepass = NULL; /* Only used for backward compatibility. */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check the username and password pair and return C_OK if they are valid,
|
/* Check the username and password pair and return C_OK if they are valid,
|
||||||
@ -1052,7 +1110,7 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
|
|||||||
int ACLAuthenticateUser(client *c, robj *username, robj *password) {
|
int ACLAuthenticateUser(client *c, robj *username, robj *password) {
|
||||||
if (ACLCheckUserCredentials(username,password) == C_OK) {
|
if (ACLCheckUserCredentials(username,password) == C_OK) {
|
||||||
c->authenticated = 1;
|
c->authenticated = 1;
|
||||||
c->puser = ACLGetUserByName((sds)ptrFromObj(username),sdslen((sds)ptrFromObj(username)));
|
c->user = ACLGetUserByName((sds)ptrFromObj(username),sdslen((sds)ptrFromObj(username)));
|
||||||
moduleNotifyUserChanged(c);
|
moduleNotifyUserChanged(c);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
} else {
|
} else {
|
||||||
@ -1068,18 +1126,16 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
|
|||||||
* command name, so that a command retains the same ID in case of modules that
|
* command name, so that a command retains the same ID in case of modules that
|
||||||
* are unloaded and later reloaded. */
|
* are unloaded and later reloaded. */
|
||||||
unsigned long ACLGetCommandID(const char *cmdname) {
|
unsigned long ACLGetCommandID(const char *cmdname) {
|
||||||
static rax *map = NULL;
|
|
||||||
static unsigned long nextid = 0;
|
|
||||||
|
|
||||||
sds lowername = sdsnew(cmdname);
|
sds lowername = sdsnew(cmdname);
|
||||||
sdstolower(lowername);
|
sdstolower(lowername);
|
||||||
if (map == NULL) map = raxNew();
|
if (commandId == NULL) commandId = raxNew();
|
||||||
void *id = raxFind(map,(unsigned char*)lowername,sdslen(lowername));
|
void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername));
|
||||||
if (id != raxNotFound) {
|
if (id != raxNotFound) {
|
||||||
sdsfree(lowername);
|
sdsfree(lowername);
|
||||||
return (unsigned long)id;
|
return (unsigned long)id;
|
||||||
}
|
}
|
||||||
raxInsert(map,(unsigned char*)lowername,strlen(lowername),
|
raxInsert(commandId,(unsigned char*)lowername,strlen(lowername),
|
||||||
(void*)nextid,NULL);
|
(void*)nextid,NULL);
|
||||||
sdsfree(lowername);
|
sdsfree(lowername);
|
||||||
unsigned long thisid = nextid;
|
unsigned long thisid = nextid;
|
||||||
@ -1097,6 +1153,13 @@ unsigned long ACLGetCommandID(const char *cmdname) {
|
|||||||
return thisid;
|
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. */
|
/* Return an username by its name, or NULL if the user does not exist. */
|
||||||
user *ACLGetUserByName(const char *name, size_t namelen) {
|
user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||||
void *myuser = raxFind(Users,(unsigned char*)name,namelen);
|
void *myuser = raxFind(Users,(unsigned char*)name,namelen);
|
||||||
@ -1106,7 +1169,7 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
|
|||||||
|
|
||||||
/* Check if the command is ready to be executed in the client 'c', already
|
/* Check if the command is ready to be executed in the client 'c', already
|
||||||
* referenced by c->cmd, and can be executed by this client according to the
|
* referenced by c->cmd, and can be executed by this client according to the
|
||||||
* ACLs associated to the client user c->puser.
|
* ACLs associated to the client user c->user.
|
||||||
*
|
*
|
||||||
* If the user can execute the command ACL_OK is returned, otherwise
|
* If the user can execute the command ACL_OK is returned, otherwise
|
||||||
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
|
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
|
||||||
@ -1114,7 +1177,7 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
|
|||||||
* command, the second if the command is denied because the user is trying
|
* command, the second if the command is denied because the user is trying
|
||||||
* to access keys that are not among the specified patterns. */
|
* to access keys that are not among the specified patterns. */
|
||||||
int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
||||||
user *u = c->puser;
|
user *u = c->user;
|
||||||
uint64_t id = c->cmd->id;
|
uint64_t id = c->cmd->id;
|
||||||
|
|
||||||
/* If there is no associated user, the connection can run anything. */
|
/* If there is no associated user, the connection can run anything. */
|
||||||
@ -1149,7 +1212,7 @@ int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
|||||||
|
|
||||||
/* Check if the user can execute commands explicitly touching the keys
|
/* Check if the user can execute commands explicitly touching the keys
|
||||||
* mentioned in the command arguments. */
|
* mentioned in the command arguments. */
|
||||||
if (!(c->puser->flags & USER_FLAG_ALLKEYS) &&
|
if (!(c->user->flags & USER_FLAG_ALLKEYS) &&
|
||||||
(c->cmd->getkeys_proc || c->cmd->firstkey))
|
(c->cmd->getkeys_proc || c->cmd->firstkey))
|
||||||
{
|
{
|
||||||
getKeysResult result = GETKEYS_RESULT_INIT;
|
getKeysResult result = GETKEYS_RESULT_INIT;
|
||||||
@ -1187,6 +1250,119 @@ int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
|||||||
return ACL_OK;
|
return ACL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check if the provided channel is whitelisted by the given allowed channels
|
||||||
|
* list. Glob-style pattern matching is employed, unless the literal flag is
|
||||||
|
* set. Returns ACL_OK if access is granted or ACL_DENIED_CHANNEL otherwise. */
|
||||||
|
int ACLCheckPubsubChannelPerm(sds channel, list *allowed, int literal) {
|
||||||
|
listIter li;
|
||||||
|
listNode *ln;
|
||||||
|
size_t clen = sdslen(channel);
|
||||||
|
int match = 0;
|
||||||
|
|
||||||
|
listRewind(allowed,&li);
|
||||||
|
while((ln = listNext(&li))) {
|
||||||
|
sds pattern = (sds)listNodeValue(ln);
|
||||||
|
size_t plen = sdslen(pattern);
|
||||||
|
|
||||||
|
if ((literal && !sdscmp(pattern,channel)) ||
|
||||||
|
(!literal && stringmatchlen(pattern,plen,channel,clen,0)))
|
||||||
|
{
|
||||||
|
match = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!match) {
|
||||||
|
return ACL_DENIED_CHANNEL;
|
||||||
|
}
|
||||||
|
return ACL_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 *u, list *upcoming) {
|
||||||
|
listIter li, lpi;
|
||||||
|
listNode *ln, *lpn;
|
||||||
|
robj *o;
|
||||||
|
int kill = 0;
|
||||||
|
|
||||||
|
/* Nothing to kill when the upcoming are a literal super set of the original
|
||||||
|
* permissions. */
|
||||||
|
listRewind(u->channels,&li);
|
||||||
|
while (!kill && ((ln = listNext(&li)) != NULL)) {
|
||||||
|
sds pattern = (sds)listNodeValue(ln);
|
||||||
|
kill = (ACLCheckPubsubChannelPerm(pattern,upcoming,1) ==
|
||||||
|
ACL_DENIED_CHANNEL);
|
||||||
|
}
|
||||||
|
if (!kill) return;
|
||||||
|
|
||||||
|
/* Scan all connected clients to find the user's pub/subs. */
|
||||||
|
listRewind(g_pserver->clients,&li);
|
||||||
|
while ((ln = listNext(&li)) != NULL) {
|
||||||
|
client *c = (client*)listNodeValue(ln);
|
||||||
|
kill = 0;
|
||||||
|
|
||||||
|
if (c->user == u && getClientType(c) == CLIENT_TYPE_PUBSUB) {
|
||||||
|
/* Check for pattern violations. */
|
||||||
|
listRewind(c->pubsub_patterns,&lpi);
|
||||||
|
while (!kill && ((lpn = listNext(&lpi)) != NULL)) {
|
||||||
|
o = (robj*)lpn->value;
|
||||||
|
kill = (ACLCheckPubsubChannelPerm(szFromObj(o),upcoming,1) ==
|
||||||
|
ACL_DENIED_CHANNEL);
|
||||||
|
}
|
||||||
|
/* Check for channel violations. */
|
||||||
|
if (!kill) {
|
||||||
|
dictIterator *di = dictGetIterator(c->pubsub_channels);
|
||||||
|
dictEntry *de;
|
||||||
|
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||||
|
o = (robj*)dictGetKey(de);
|
||||||
|
kill = (ACLCheckPubsubChannelPerm(szFromObj(o),upcoming,0) ==
|
||||||
|
ACL_DENIED_CHANNEL);
|
||||||
|
}
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kill it. */
|
||||||
|
if (kill) {
|
||||||
|
freeClientAsync(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the pub/sub channels of the command, that's ready to be executed in
|
||||||
|
* the client 'c', can be executed by this client according to the ACLs channels
|
||||||
|
* associated to the client user c->user.
|
||||||
|
*
|
||||||
|
* idx and count are the index and count of channel arguments from the
|
||||||
|
* command. The literal argument controls whether the user's ACL channels are
|
||||||
|
* evaluated as literal values or matched as glob-like patterns.
|
||||||
|
*
|
||||||
|
* If the user can execute the command ACL_OK is returned, otherwise
|
||||||
|
* ACL_DENIED_CHANNEL. */
|
||||||
|
int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr) {
|
||||||
|
user *u = c->user;
|
||||||
|
|
||||||
|
/* If there is no associated user, the connection can run anything. */
|
||||||
|
if (u == NULL) return ACL_OK;
|
||||||
|
|
||||||
|
/* Check if the user can access the channels mentioned in the command's
|
||||||
|
* arguments. */
|
||||||
|
if (!(c->user->flags & USER_FLAG_ALLCHANNELS)) {
|
||||||
|
for (int j = idx; j < idx+count; j++) {
|
||||||
|
if (ACLCheckPubsubChannelPerm(szFromObj(c->argv[j]),u->channels,literal)
|
||||||
|
!= ACL_OK) {
|
||||||
|
if (idxptr) *idxptr = j;
|
||||||
|
return ACL_DENIED_CHANNEL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we survived all the above checks, the user can execute the
|
||||||
|
* command. */
|
||||||
|
return ACL_OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
* ACL loading / saving functions
|
* ACL loading / saving functions
|
||||||
* ==========================================================================*/
|
* ==========================================================================*/
|
||||||
@ -1588,8 +1764,8 @@ int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Release an ACL log entry. */
|
/* Release an ACL log entry. */
|
||||||
void ACLFreeLogEntry(ACLLogEntry *leptr) {
|
void ACLFreeLogEntry(const void *leptr) {
|
||||||
ACLLogEntry *le = leptr;
|
ACLLogEntry *le = (ACLLogEntry*)leptr;
|
||||||
sdsfree(le->object);
|
sdsfree(le->object);
|
||||||
sdsfree(le->username);
|
sdsfree(le->username);
|
||||||
sdsfree(le->cinfo);
|
sdsfree(le->cinfo);
|
||||||
@ -1602,24 +1778,25 @@ void ACLFreeLogEntry(ACLLogEntry *leptr) {
|
|||||||
* the log entry instead of creating many entries for very similar ACL
|
* the log entry instead of creating many entries for very similar ACL
|
||||||
* rules issues.
|
* rules issues.
|
||||||
*
|
*
|
||||||
* The keypos argument is only used when the reason is ACL_DENIED_KEY, since
|
* The argpos argument is used when the reason is ACL_DENIED_KEY or
|
||||||
* it allows the function to log the key name that caused the problem.
|
* ACL_DENIED_CHANNEL, since it allows the function to log the key or channel
|
||||||
* Similarly the username is only passed when we failed to authenticate the
|
* name that caused the problem. Similarly the username is only passed when we
|
||||||
* user with AUTH or HELLO, for the ACL_DENIED_AUTH reason. Otherwise
|
* failed to authenticate the user with AUTH or HELLO, for the ACL_DENIED_AUTH
|
||||||
* it will just be NULL.
|
* reason. Otherwise it will just be NULL.
|
||||||
*/
|
*/
|
||||||
void addACLLogEntry(client *c, int reason, int keypos, sds username) {
|
void addACLLogEntry(client *c, int reason, int argpos, sds username) {
|
||||||
/* Create a new entry. */
|
/* Create a new entry. */
|
||||||
struct ACLLogEntry *le = (ACLLogEntry*)zmalloc(sizeof(*le));
|
struct ACLLogEntry *le = (ACLLogEntry*)zmalloc(sizeof(*le));
|
||||||
le->count = 1;
|
le->count = 1;
|
||||||
le->reason = reason;
|
le->reason = reason;
|
||||||
le->username = sdsdup(reason == ACL_DENIED_AUTH ? username : c->puser->name);
|
le->username = sdsdup(reason == ACL_DENIED_AUTH ? username : c->user->name);
|
||||||
le->ctime = mstime();
|
le->ctime = mstime();
|
||||||
|
|
||||||
switch(reason) {
|
switch(reason) {
|
||||||
case ACL_DENIED_CMD: le->object = sdsnew(c->cmd->name); break;
|
case ACL_DENIED_CMD: le->object = sdsnew(c->cmd->name); break;
|
||||||
case ACL_DENIED_KEY: le->object = sdsnew(szFromObj(c->argv[keypos])); break;
|
case ACL_DENIED_KEY: le->object = sdsdup(szFromObj(c->argv[argpos])); break;
|
||||||
case ACL_DENIED_AUTH: le->object = sdsnew(szFromObj(c->argv[0])); break;
|
case ACL_DENIED_CHANNEL: le->object = sdsdup(szFromObj(c->argv[argpos])); break;
|
||||||
|
case ACL_DENIED_AUTH: le->object = sdsdup(szFromObj(c->argv[0])); break;
|
||||||
default: le->object = sdsempty();
|
default: le->object = sdsempty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1666,7 +1843,7 @@ void addACLLogEntry(client *c, int reason, int keypos, sds username) {
|
|||||||
le->cinfo = NULL;
|
le->cinfo = NULL;
|
||||||
ACLFreeLogEntry(le);
|
ACLFreeLogEntry(le);
|
||||||
} else {
|
} else {
|
||||||
/* Add it to our list of entires. We'll have to trim the list
|
/* Add it to our list of entries. We'll have to trim the list
|
||||||
* to its maximum size. */
|
* to its maximum size. */
|
||||||
listAddNodeHead(ACLLog, le);
|
listAddNodeHead(ACLLog, le);
|
||||||
while(listLength(ACLLog) > g_pserver->acllog_max_len) {
|
while(listLength(ACLLog) > g_pserver->acllog_max_len) {
|
||||||
@ -1727,6 +1904,11 @@ void aclCommand(client *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Existing pub/sub clients authenticated with the user may need to be
|
||||||
|
* disconnected if (some of) their channel permissions were revoked. */
|
||||||
|
if (u && !(tempu->flags & USER_FLAG_ALLCHANNELS))
|
||||||
|
ACLKillPubsubClientsIfNeeded(u,tempu->channels);
|
||||||
|
|
||||||
/* Overwrite the user with the temporary user we modified above. */
|
/* Overwrite the user with the temporary user we modified above. */
|
||||||
if (!u) u = ACLCreateUser(username,sdslen(username));
|
if (!u) u = ACLCreateUser(username,sdslen(username));
|
||||||
serverAssert(u != NULL);
|
serverAssert(u != NULL);
|
||||||
@ -1762,7 +1944,7 @@ void aclCommand(client *c) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addReplyMapLen(c,4);
|
addReplyMapLen(c,5);
|
||||||
|
|
||||||
/* Flags */
|
/* Flags */
|
||||||
addReplyBulkCString(c,"flags");
|
addReplyBulkCString(c,"flags");
|
||||||
@ -1807,6 +1989,22 @@ void aclCommand(client *c) {
|
|||||||
addReplyBulkCBuffer(c,thispat,sdslen(thispat));
|
addReplyBulkCBuffer(c,thispat,sdslen(thispat));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pub/sub patterns */
|
||||||
|
addReplyBulkCString(c,"channels");
|
||||||
|
if (u->flags & USER_FLAG_ALLCHANNELS) {
|
||||||
|
addReplyArrayLen(c,1);
|
||||||
|
addReplyBulkCBuffer(c,"*",1);
|
||||||
|
} else {
|
||||||
|
addReplyArrayLen(c,listLength(u->channels));
|
||||||
|
listIter li;
|
||||||
|
listNode *ln;
|
||||||
|
listRewind(u->channels,&li);
|
||||||
|
while((ln = listNext(&li))) {
|
||||||
|
sds thispat = (sds)listNodeValue(ln);
|
||||||
|
addReplyBulkCBuffer(c,thispat,sdslen(thispat));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) &&
|
} else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) &&
|
||||||
c->argc == 2)
|
c->argc == 2)
|
||||||
{
|
{
|
||||||
@ -1832,8 +2030,8 @@ void aclCommand(client *c) {
|
|||||||
}
|
}
|
||||||
raxStop(&ri);
|
raxStop(&ri);
|
||||||
} else if (!strcasecmp(sub,"whoami") && c->argc == 2) {
|
} else if (!strcasecmp(sub,"whoami") && c->argc == 2) {
|
||||||
if (c->puser != NULL) {
|
if (c->user != NULL) {
|
||||||
addReplyBulkCBuffer(c,c->puser->name,sdslen(c->puser->name));
|
addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
|
||||||
} else {
|
} else {
|
||||||
addReplyNull(c);
|
addReplyNull(c);
|
||||||
}
|
}
|
||||||
@ -1911,7 +2109,7 @@ void aclCommand(client *c) {
|
|||||||
* the "RESET" command in order to flush the old entries. */
|
* the "RESET" command in order to flush the old entries. */
|
||||||
if (c->argc == 3) {
|
if (c->argc == 3) {
|
||||||
if (!strcasecmp(szFromObj(c->argv[2]),"reset")) {
|
if (!strcasecmp(szFromObj(c->argv[2]),"reset")) {
|
||||||
listSetFreeMethod(ACLLog,(void(*)(const void*))ACLFreeLogEntry);
|
listSetFreeMethod(ACLLog,ACLFreeLogEntry);
|
||||||
listEmpty(ACLLog);
|
listEmpty(ACLLog);
|
||||||
listSetFreeMethod(ACLLog,NULL);
|
listSetFreeMethod(ACLLog,NULL);
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
@ -1940,10 +2138,11 @@ void aclCommand(client *c) {
|
|||||||
addReplyLongLong(c,le->count);
|
addReplyLongLong(c,le->count);
|
||||||
|
|
||||||
addReplyBulkCString(c,"reason");
|
addReplyBulkCString(c,"reason");
|
||||||
const char *reasonstr = "INVALID_REASON";
|
const char *reasonstr;
|
||||||
switch(le->reason) {
|
switch(le->reason) {
|
||||||
case ACL_DENIED_CMD: reasonstr="command"; break;
|
case ACL_DENIED_CMD: reasonstr="command"; break;
|
||||||
case ACL_DENIED_KEY: reasonstr="key"; break;
|
case ACL_DENIED_KEY: reasonstr="key"; break;
|
||||||
|
case ACL_DENIED_CHANNEL: reasonstr="channel"; break;
|
||||||
case ACL_DENIED_AUTH: reasonstr="auth"; break;
|
case ACL_DENIED_AUTH: reasonstr="auth"; break;
|
||||||
default: reasonstr="unknown";
|
default: reasonstr="unknown";
|
||||||
}
|
}
|
||||||
@ -1971,18 +2170,30 @@ void aclCommand(client *c) {
|
|||||||
}
|
}
|
||||||
} else if (c->argc == 2 && !strcasecmp(sub,"help")) {
|
} else if (c->argc == 2 && !strcasecmp(sub,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"LOAD -- Reload users from the ACL file.",
|
"CAT [<category>]",
|
||||||
"SAVE -- Save the current config to the ACL file.",
|
" List all commands that belong to <category>, or all command categories",
|
||||||
"LIST -- Show user details in config file format.",
|
" when no category is specified.",
|
||||||
"USERS -- List all the registered usernames.",
|
"DELUSER <username> [<username> ...]",
|
||||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
" Delete a list of users.",
|
||||||
"GETUSER <username> -- Get the user details.",
|
"GETUSER <username>",
|
||||||
"DELUSER <username> [...] -- Delete a list of users.",
|
" Get the user's details.",
|
||||||
"CAT -- List available categories.",
|
"GENPASS [<bits>]",
|
||||||
"CAT <category> -- List commands inside category.",
|
" Generate a secure 256-bit user password. The optional `bits` argument can",
|
||||||
"GENPASS [<bits>] -- Generate a secure user password.",
|
" be used to specify a different size.",
|
||||||
"WHOAMI -- Return the current connection username.",
|
"LIST",
|
||||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
" Show users details in config file format.",
|
||||||
|
"LOAD",
|
||||||
|
" Reload users from the ACL file.",
|
||||||
|
"LOG [<count> | RESET]",
|
||||||
|
" Show the ACL log entries.",
|
||||||
|
"SAVE",
|
||||||
|
" Save the current config to the ACL file.",
|
||||||
|
"SETUSER <username> <attribute> [<attribute> ...]",
|
||||||
|
" Create or modify a user with the specified attributes.",
|
||||||
|
"USERS",
|
||||||
|
" List all the registered usernames.",
|
||||||
|
"WHOAMI",
|
||||||
|
" Return the current connection username.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c,help);
|
addReplyHelp(c,help);
|
||||||
@ -2011,7 +2222,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
|
|||||||
void authCommand(client *c) {
|
void authCommand(client *c) {
|
||||||
/* Only two or three argument forms are allowed. */
|
/* Only two or three argument forms are allowed. */
|
||||||
if (c->argc > 3) {
|
if (c->argc > 3) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2028,7 +2239,7 @@ void authCommand(client *c) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
username = createStringObject("default",7);
|
username = shared.default_username;
|
||||||
password = c->argv[1];
|
password = c->argv[1];
|
||||||
} else {
|
} else {
|
||||||
username = c->argv[1];
|
username = c->argv[1];
|
||||||
@ -2038,11 +2249,19 @@ void authCommand(client *c) {
|
|||||||
if (ACLAuthenticateUser(c,username,password) == C_OK) {
|
if (ACLAuthenticateUser(c,username,password) == C_OK) {
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
} else {
|
} else {
|
||||||
addReplyError(c,"-WRONGPASS invalid username-password pair");
|
addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free the "default" string object we created for the two
|
/* Set the password for the "default" ACL user. This implements supports for
|
||||||
* arguments form. */
|
* requirepass config, so passing in NULL will set the user to be nopass. */
|
||||||
if (c->argc == 2) decrRefCount(username);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +360,8 @@ void listRotateHeadToTail(list *list) {
|
|||||||
/* Add all the elements of the list 'o' at the end of the
|
/* Add all the elements of the list 'o' at the end of the
|
||||||
* list 'l'. The list 'other' remains empty but otherwise valid. */
|
* list 'l'. The list 'other' remains empty but otherwise valid. */
|
||||||
void listJoin(list *l, list *o) {
|
void listJoin(list *l, list *o) {
|
||||||
if (o->head)
|
if (o->len == 0) return;
|
||||||
|
|
||||||
o->head->prev = l->tail;
|
o->head->prev = l->tail;
|
||||||
|
|
||||||
if (l->tail)
|
if (l->tail)
|
||||||
@ -368,7 +369,7 @@ void listJoin(list *l, list *o) {
|
|||||||
else
|
else
|
||||||
l->head = o->head;
|
l->head = o->head;
|
||||||
|
|
||||||
if (o->tail) l->tail = o->tail;
|
l->tail = o->tail;
|
||||||
l->len += o->len;
|
l->len += o->len;
|
||||||
|
|
||||||
/* Setup other as an empty list. */
|
/* Setup other as an empty list. */
|
||||||
|
120
src/ae.cpp
120
src/ae.cpp
@ -30,6 +30,10 @@
|
|||||||
* POSSIBILITY OF SUCH DAMAGE.
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "ae.h"
|
||||||
|
#include "anet.h"
|
||||||
|
#include "fastlock.h"
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -271,12 +275,13 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
|||||||
aeEventLoop *eventLoop;
|
aeEventLoop *eventLoop;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
monotonicInit(); /* just in case the calling app didn't initialize */
|
||||||
|
|
||||||
if ((eventLoop = (aeEventLoop*)zmalloc(sizeof(*eventLoop), MALLOC_LOCAL)) == NULL) goto err;
|
if ((eventLoop = (aeEventLoop*)zmalloc(sizeof(*eventLoop), MALLOC_LOCAL)) == NULL) goto err;
|
||||||
eventLoop->events = (aeFileEvent*)zmalloc(sizeof(aeFileEvent)*setsize, MALLOC_LOCAL);
|
eventLoop->events = (aeFileEvent*)zmalloc(sizeof(aeFileEvent)*setsize, MALLOC_LOCAL);
|
||||||
eventLoop->fired = (aeFiredEvent*)zmalloc(sizeof(aeFiredEvent)*setsize, MALLOC_LOCAL);
|
eventLoop->fired = (aeFiredEvent*)zmalloc(sizeof(aeFiredEvent)*setsize, MALLOC_LOCAL);
|
||||||
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
|
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
|
||||||
eventLoop->setsize = setsize;
|
eventLoop->setsize = setsize;
|
||||||
eventLoop->lastTime = time(NULL);
|
|
||||||
eventLoop->timeEventHead = NULL;
|
eventLoop->timeEventHead = NULL;
|
||||||
eventLoop->timeEventNextId = 0;
|
eventLoop->timeEventNextId = 0;
|
||||||
eventLoop->stop = 0;
|
eventLoop->stop = 0;
|
||||||
@ -448,29 +453,6 @@ extern "C" int aeGetFileEvents(aeEventLoop *eventLoop, int fd) {
|
|||||||
return fe->mask;
|
return fe->mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void aeGetTime(long *seconds, long *milliseconds)
|
|
||||||
{
|
|
||||||
struct timeval tv;
|
|
||||||
|
|
||||||
gettimeofday(&tv, NULL);
|
|
||||||
*seconds = tv.tv_sec;
|
|
||||||
*milliseconds = tv.tv_usec/1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) {
|
|
||||||
long cur_sec, cur_ms, when_sec, when_ms;
|
|
||||||
|
|
||||||
aeGetTime(&cur_sec, &cur_ms);
|
|
||||||
when_sec = cur_sec + milliseconds/1000;
|
|
||||||
when_ms = cur_ms + milliseconds%1000;
|
|
||||||
if (when_ms >= 1000) {
|
|
||||||
when_sec ++;
|
|
||||||
when_ms -= 1000;
|
|
||||||
}
|
|
||||||
*sec = when_sec;
|
|
||||||
*ms = when_ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
|
extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
|
||||||
aeTimeProc *proc, void *clientData,
|
aeTimeProc *proc, void *clientData,
|
||||||
aeEventFinalizerProc *finalizerProc)
|
aeEventFinalizerProc *finalizerProc)
|
||||||
@ -482,7 +464,7 @@ extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long millise
|
|||||||
te = (aeTimeEvent*)zmalloc(sizeof(*te), MALLOC_LOCAL);
|
te = (aeTimeEvent*)zmalloc(sizeof(*te), MALLOC_LOCAL);
|
||||||
if (te == NULL) return AE_ERR;
|
if (te == NULL) return AE_ERR;
|
||||||
te->id = id;
|
te->id = id;
|
||||||
aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
|
te->when = getMonotonicUs() + milliseconds * 1000;
|
||||||
te->timeProc = proc;
|
te->timeProc = proc;
|
||||||
te->finalizerProc = finalizerProc;
|
te->finalizerProc = finalizerProc;
|
||||||
te->clientData = clientData;
|
te->clientData = clientData;
|
||||||
@ -509,10 +491,8 @@ extern "C" int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
|
|||||||
return AE_ERR; /* NO event with the specified ID found */
|
return AE_ERR; /* NO event with the specified ID found */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search the first timer to fire.
|
/* How many milliseconds until the first timer should fire.
|
||||||
* This operation is useful to know how many time the select can be
|
* If there are no timers, -1 is returned.
|
||||||
* put in sleep without to delay any event.
|
|
||||||
* If there are no timers NULL is returned.
|
|
||||||
*
|
*
|
||||||
* Note that's O(N) since time events are unsorted.
|
* Note that's O(N) since time events are unsorted.
|
||||||
* Possible optimizations (not needed by Redis so far, but...):
|
* Possible optimizations (not needed by Redis so far, but...):
|
||||||
@ -520,20 +500,20 @@ extern "C" int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
|
|||||||
* Much better but still insertion or deletion of timers is O(N).
|
* 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)).
|
* 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
|
||||||
*/
|
*/
|
||||||
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
|
static long msUntilEarliestTimer(aeEventLoop *eventLoop) {
|
||||||
{
|
|
||||||
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
|
|
||||||
aeTimeEvent *te = eventLoop->timeEventHead;
|
aeTimeEvent *te = eventLoop->timeEventHead;
|
||||||
aeTimeEvent *nearest = NULL;
|
if (te == NULL) return -1;
|
||||||
|
|
||||||
|
aeTimeEvent *earliest = NULL;
|
||||||
while (te) {
|
while (te) {
|
||||||
if (!nearest || te->when_sec < nearest->when_sec ||
|
if (!earliest || te->when < earliest->when)
|
||||||
(te->when_sec == nearest->when_sec &&
|
earliest = te;
|
||||||
te->when_ms < nearest->when_ms))
|
|
||||||
nearest = te;
|
|
||||||
te = te->next;
|
te = te->next;
|
||||||
}
|
}
|
||||||
return nearest;
|
|
||||||
|
monotime now = getMonotonicUs();
|
||||||
|
return (now >= earliest->when)
|
||||||
|
? 0 : (long)((earliest->when - now) / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Process time events */
|
/* Process time events */
|
||||||
@ -542,29 +522,11 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
|||||||
int processed = 0;
|
int processed = 0;
|
||||||
aeTimeEvent *te;
|
aeTimeEvent *te;
|
||||||
long long maxId;
|
long long maxId;
|
||||||
time_t now = time(NULL);
|
|
||||||
|
|
||||||
/* If the system clock is moved to the future, and then set back to the
|
|
||||||
* right value, time events may be delayed in a random way. Often this
|
|
||||||
* means that scheduled operations will not be performed soon enough.
|
|
||||||
*
|
|
||||||
* Here we try to detect system clock skews, and force all the time
|
|
||||||
* events to be processed ASAP when this happens: the idea is that
|
|
||||||
* processing events earlier is less dangerous than delaying them
|
|
||||||
* indefinitely, and practice suggests it is. */
|
|
||||||
if (now < eventLoop->lastTime) {
|
|
||||||
te = eventLoop->timeEventHead;
|
|
||||||
while(te) {
|
|
||||||
te->when_sec = 0;
|
|
||||||
te = te->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventLoop->lastTime = now;
|
|
||||||
|
|
||||||
te = eventLoop->timeEventHead;
|
te = eventLoop->timeEventHead;
|
||||||
maxId = eventLoop->timeEventNextId-1;
|
maxId = eventLoop->timeEventNextId-1;
|
||||||
|
monotime now = getMonotonicUs();
|
||||||
while(te) {
|
while(te) {
|
||||||
long now_sec, now_ms;
|
|
||||||
long long id;
|
long long id;
|
||||||
|
|
||||||
/* Remove events scheduled for deletion. */
|
/* Remove events scheduled for deletion. */
|
||||||
@ -586,6 +548,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
|||||||
if (te->finalizerProc) {
|
if (te->finalizerProc) {
|
||||||
if (!ulock.owns_lock()) ulock.lock();
|
if (!ulock.owns_lock()) ulock.lock();
|
||||||
te->finalizerProc(eventLoop, te->clientData);
|
te->finalizerProc(eventLoop, te->clientData);
|
||||||
|
now = getMonotonicUs();
|
||||||
}
|
}
|
||||||
zfree(te);
|
zfree(te);
|
||||||
te = next;
|
te = next;
|
||||||
@ -601,10 +564,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
|||||||
te = te->next;
|
te = te->next;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
aeGetTime(&now_sec, &now_ms);
|
|
||||||
if (now_sec > te->when_sec ||
|
if (te->when <= now) {
|
||||||
(now_sec == te->when_sec && now_ms >= te->when_ms))
|
|
||||||
{
|
|
||||||
if (!ulock.owns_lock()) ulock.lock();
|
if (!ulock.owns_lock()) ulock.lock();
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
@ -613,8 +574,9 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
|||||||
retval = te->timeProc(eventLoop, id, te->clientData);
|
retval = te->timeProc(eventLoop, id, te->clientData);
|
||||||
te->refcount--;
|
te->refcount--;
|
||||||
processed++;
|
processed++;
|
||||||
|
now = getMonotonicUs();
|
||||||
if (retval != AE_NOMORE) {
|
if (retval != AE_NOMORE) {
|
||||||
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
|
te->when = now + retval * 1000;
|
||||||
} else {
|
} else {
|
||||||
te->id = AE_DELETED_EVENT_ID;
|
te->id = AE_DELETED_EVENT_ID;
|
||||||
}
|
}
|
||||||
@ -704,37 +666,23 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
|||||||
/* Nothing to do? return ASAP */
|
/* Nothing to do? return ASAP */
|
||||||
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
|
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
|
||||||
|
|
||||||
/* Note that we want call select() even if there are no
|
/* Note that we want to call select() even if there are no
|
||||||
* file events to process as long as we want to process time
|
* file events to process as long as we want to process time
|
||||||
* events, in order to sleep until the next time event is ready
|
* events, in order to sleep until the next time event is ready
|
||||||
* to fire. */
|
* to fire. */
|
||||||
if (eventLoop->maxfd != -1 ||
|
if (eventLoop->maxfd != -1 ||
|
||||||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
|
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
|
||||||
int j;
|
int j;
|
||||||
aeTimeEvent *shortest = NULL;
|
|
||||||
struct timeval tv, *tvp;
|
struct timeval tv, *tvp;
|
||||||
|
long msUntilTimer = -1;
|
||||||
|
|
||||||
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
|
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
|
||||||
shortest = aeSearchNearestTimer(eventLoop);
|
msUntilTimer = msUntilEarliestTimer(eventLoop);
|
||||||
if (shortest) {
|
|
||||||
long now_sec, now_ms;
|
|
||||||
|
|
||||||
aeGetTime(&now_sec, &now_ms);
|
if (msUntilTimer >= 0) {
|
||||||
|
tv.tv_sec = msUntilTimer / 1000;
|
||||||
|
tv.tv_usec = (msUntilTimer % 1000) * 1000;
|
||||||
tvp = &tv;
|
tvp = &tv;
|
||||||
|
|
||||||
/* How many milliseconds we need to wait for the next
|
|
||||||
* time event to fire? */
|
|
||||||
long long ms =
|
|
||||||
(shortest->when_sec - now_sec)*1000 +
|
|
||||||
shortest->when_ms - now_ms;
|
|
||||||
|
|
||||||
if (ms > 0) {
|
|
||||||
tvp->tv_sec = ms/1000;
|
|
||||||
tvp->tv_usec = (ms % 1000)*1000;
|
|
||||||
} else {
|
|
||||||
tvp->tv_sec = 0;
|
|
||||||
tvp->tv_usec = 0;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/* If we have to check for events but need to return
|
/* If we have to check for events but need to return
|
||||||
* ASAP because of AE_DONT_WAIT we need to set the timeout
|
* ASAP because of AE_DONT_WAIT we need to set the timeout
|
||||||
@ -816,14 +764,8 @@ void aeMain(aeEventLoop *eventLoop) {
|
|||||||
eventLoop->stop = 0;
|
eventLoop->stop = 0;
|
||||||
g_eventLoopThisThread = eventLoop;
|
g_eventLoopThisThread = eventLoop;
|
||||||
while (!eventLoop->stop) {
|
while (!eventLoop->stop) {
|
||||||
if (eventLoop->beforesleep != NULL) {
|
|
||||||
std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock);
|
|
||||||
if (!(eventLoop->beforesleepFlags & AE_SLEEP_THREADSAFE))
|
|
||||||
ulock.lock();
|
|
||||||
eventLoop->beforesleep(eventLoop);
|
|
||||||
}
|
|
||||||
serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing
|
serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing
|
||||||
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
|
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP);
|
||||||
serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing
|
serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
src/ae.h
6
src/ae.h
@ -36,7 +36,7 @@
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#endif
|
#endif
|
||||||
#include <time.h>
|
#include "monotonic.h"
|
||||||
#include "fastlock.h"
|
#include "fastlock.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@ -91,8 +91,7 @@ typedef struct aeFileEvent {
|
|||||||
/* Time event structure */
|
/* Time event structure */
|
||||||
typedef struct aeTimeEvent {
|
typedef struct aeTimeEvent {
|
||||||
long long id; /* time event identifier. */
|
long long id; /* time event identifier. */
|
||||||
long when_sec; /* seconds */
|
monotime when;
|
||||||
long when_ms; /* milliseconds */
|
|
||||||
aeTimeProc *timeProc;
|
aeTimeProc *timeProc;
|
||||||
aeEventFinalizerProc *finalizerProc;
|
aeEventFinalizerProc *finalizerProc;
|
||||||
void *clientData;
|
void *clientData;
|
||||||
@ -113,7 +112,6 @@ typedef struct aeEventLoop {
|
|||||||
int maxfd; /* highest file descriptor currently registered */
|
int maxfd; /* highest file descriptor currently registered */
|
||||||
int setsize; /* max number of file descriptors tracked */
|
int setsize; /* max number of file descriptors tracked */
|
||||||
long long timeEventNextId;
|
long long timeEventNextId;
|
||||||
time_t lastTime; /* Used to detect system clock skew */
|
|
||||||
aeFileEvent *events; /* Registered events */
|
aeFileEvent *events; /* Registered events */
|
||||||
aeFiredEvent *fired; /* Fired events */
|
aeFiredEvent *fired; /* Fired events */
|
||||||
aeTimeEvent *timeEventHead;
|
aeTimeEvent *timeEventHead;
|
||||||
|
@ -51,6 +51,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
|||||||
zfree(state);
|
zfree(state);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
anetCloexec(state->epfd);
|
||||||
eventLoop->apidata = state;
|
eventLoop->apidata = state;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ static int evport_debug = 0;
|
|||||||
|
|
||||||
typedef struct aeApiState {
|
typedef struct aeApiState {
|
||||||
int portfd; /* event port */
|
int portfd; /* event port */
|
||||||
int npending; /* # of pending fds */
|
uint_t npending; /* # of pending fds */
|
||||||
int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */
|
int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */
|
||||||
int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */
|
int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */
|
||||||
} aeApiState;
|
} aeApiState;
|
||||||
@ -82,6 +82,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
|||||||
zfree(state);
|
zfree(state);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
anetCloexec(state->portfd);
|
||||||
|
|
||||||
state->npending = 0;
|
state->npending = 0;
|
||||||
|
|
||||||
@ -95,6 +96,8 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||||
|
(void) eventLoop;
|
||||||
|
(void) setsize;
|
||||||
/* Nothing to resize here. */
|
/* Nothing to resize here. */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -107,7 +110,7 @@ static void aeApiFree(aeEventLoop *eventLoop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int aeApiLookupPending(aeApiState *state, int fd) {
|
static int aeApiLookupPending(aeApiState *state, int fd) {
|
||||||
int i;
|
uint_t i;
|
||||||
|
|
||||||
for (i = 0; i < state->npending; i++) {
|
for (i = 0; i < state->npending; i++) {
|
||||||
if (state->pending_fds[i] == fd)
|
if (state->pending_fds[i] == fd)
|
||||||
@ -243,7 +246,7 @@ static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) {
|
|||||||
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||||
aeApiState *state = eventLoop->apidata;
|
aeApiState *state = eventLoop->apidata;
|
||||||
struct timespec timeout, *tsp;
|
struct timespec timeout, *tsp;
|
||||||
int mask, i;
|
uint_t mask, i;
|
||||||
uint_t nevents;
|
uint_t nevents;
|
||||||
port_event_t event[MAX_EVENT_BATCHSZ];
|
port_event_t event[MAX_EVENT_BATCHSZ];
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
|||||||
zfree(state);
|
zfree(state);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
anetCloexec(state->kqfd);
|
||||||
eventLoop->apidata = state;
|
eventLoop->apidata = state;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
98
src/anet.c
98
src/anet.c
@ -69,6 +69,11 @@ int anetSetBlock(char *err, int fd, int non_block) {
|
|||||||
return ANET_ERR;
|
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)
|
if (non_block)
|
||||||
flags |= O_NONBLOCK;
|
flags |= O_NONBLOCK;
|
||||||
else
|
else
|
||||||
@ -89,6 +94,29 @@ int anetBlock(char *err, int fd) {
|
|||||||
return anetSetBlock(err,fd,0);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/* Set TCP keep alive option to detect dead peers. The interval option
|
/* Set TCP keep alive option to detect dead peers. The interval option
|
||||||
* is only used for Linux as we are using Linux-specific APIs to set
|
* is only used for Linux as we are using Linux-specific APIs to set
|
||||||
* the probe send time, interval, and count. */
|
* the probe send time, interval, and count. */
|
||||||
@ -207,14 +235,13 @@ int anetRecvTimeout(char *err, int fd, long long ms) {
|
|||||||
return ANET_OK;
|
return ANET_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
|
/* Resolve the hostname "host" and set the string representation of the
|
||||||
* do the actual work. It resolves the hostname "host" and set the string
|
* IP address into the buffer pointed by "ipbuf".
|
||||||
* representation of the IP address into the buffer pointed by "ipbuf".
|
|
||||||
*
|
*
|
||||||
* If flags is set to ANET_IP_ONLY the function only resolves hostnames
|
* 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
|
* that are actually already IPv4 or IPv6 addresses. This turns the function
|
||||||
* into a validating / normalizing function. */
|
* into a validating / normalizing function. */
|
||||||
int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
||||||
int flags)
|
int flags)
|
||||||
{
|
{
|
||||||
struct addrinfo hints, *info;
|
struct addrinfo hints, *info;
|
||||||
@ -241,14 +268,6 @@ int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
|||||||
return ANET_OK;
|
return ANET_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
|
|
||||||
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
|
|
||||||
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_IP_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int anetSetReuseAddr(char *err, int fd) {
|
static int anetSetReuseAddr(char *err, int fd) {
|
||||||
int yes = 1;
|
int yes = 1;
|
||||||
/* Make sure connection-intensive things like the redis benchmark
|
/* Make sure connection-intensive things like the redis benchmark
|
||||||
@ -484,13 +503,12 @@ static int anetV6Only(char *err, int s) {
|
|||||||
int yes = 1;
|
int yes = 1;
|
||||||
if (setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,&yes,sizeof(yes)) == -1) {
|
if (setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,&yes,sizeof(yes)) == -1) {
|
||||||
anetSetError(err, "setsockopt: %s", strerror(errno));
|
anetSetError(err, "setsockopt: %s", strerror(errno));
|
||||||
close(s);
|
|
||||||
return ANET_ERR;
|
return ANET_ERR;
|
||||||
}
|
}
|
||||||
return ANET_OK;
|
return ANET_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort, int fFirstListen)
|
static int _anetTcpServer(char *err, int port, const char *bindaddr, int af, int backlog, int fReusePort, int fFirstListen)
|
||||||
{
|
{
|
||||||
int s = -1, rv;
|
int s = -1, rv;
|
||||||
char _port[6]; /* strlen("65535") */
|
char _port[6]; /* strlen("65535") */
|
||||||
@ -501,6 +519,10 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
|
|||||||
hints.ai_family = af;
|
hints.ai_family = af;
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */
|
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) {
|
if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {
|
||||||
anetSetError(err, "%s", gai_strerror(rv));
|
anetSetError(err, "%s", gai_strerror(rv));
|
||||||
@ -533,12 +555,12 @@ end:
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
int anetTcpServer(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
||||||
{
|
{
|
||||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort, fFirstListen);
|
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort, fFirstListen);
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
int anetTcp6Server(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
||||||
{
|
{
|
||||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort, fFirstListen);
|
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort, fFirstListen);
|
||||||
}
|
}
|
||||||
@ -607,11 +629,15 @@ int anetUnixAccept(char *err, int s) {
|
|||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
|
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type) {
|
||||||
struct sockaddr_storage sa;
|
struct sockaddr_storage sa;
|
||||||
socklen_t salen = sizeof(sa);
|
socklen_t salen = sizeof(sa);
|
||||||
|
|
||||||
|
if (fd_to_str_type == FD_TO_PEER_NAME) {
|
||||||
if (getpeername(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
|
if (getpeername(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
|
||||||
|
} else {
|
||||||
|
if (getsockname(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
|
||||||
|
}
|
||||||
if (ip_len == 0) goto error;
|
if (ip_len == 0) goto error;
|
||||||
|
|
||||||
if (sa.ss_family == AF_INET) {
|
if (sa.ss_family == AF_INET) {
|
||||||
@ -623,7 +649,7 @@ int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
|
|||||||
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
|
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
|
||||||
if (port) *port = ntohs(s->sin6_port);
|
if (port) *port = ntohs(s->sin6_port);
|
||||||
} else if (sa.ss_family == AF_UNIX) {
|
} else if (sa.ss_family == AF_UNIX) {
|
||||||
if (ip) strncpy(ip,"/unixsocket",ip_len);
|
if (ip) snprintf(ip, ip_len, "/unixsocket");
|
||||||
if (port) *port = 0;
|
if (port) *port = 0;
|
||||||
} else {
|
} else {
|
||||||
goto error;
|
goto error;
|
||||||
@ -651,41 +677,11 @@ int anetFormatAddr(char *buf, size_t buf_len, char *ip, int port) {
|
|||||||
"[%s]:%d" : "%s:%d", ip, port);
|
"[%s]:%d" : "%s:%d", ip, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Like anetFormatAddr() but extract ip and port from the socket's peer. */
|
/* Like anetFormatAddr() but extract ip and port from the socket's peer/sockname. */
|
||||||
int anetFormatPeer(int fd, char *buf, size_t buf_len) {
|
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type) {
|
||||||
char ip[INET6_ADDRSTRLEN];
|
char ip[INET6_ADDRSTRLEN];
|
||||||
int port;
|
int port;
|
||||||
|
|
||||||
anetPeerToString(fd,ip,sizeof(ip),&port);
|
anetFdToString(fd,ip,sizeof(ip),&port,fd_to_str_type);
|
||||||
return anetFormatAddr(buf, buf_len, ip, port);
|
return anetFormatAddr(buf, buf_len, ip, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
|
|
||||||
struct sockaddr_storage sa;
|
|
||||||
socklen_t salen = sizeof(sa);
|
|
||||||
|
|
||||||
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
|
||||||
if (port) *port = 0;
|
|
||||||
ip[0] = '?';
|
|
||||||
ip[1] = '\0';
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
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 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int anetFormatSock(int fd, char *fmt, size_t fmt_len) {
|
|
||||||
char ip[INET6_ADDRSTRLEN];
|
|
||||||
int port;
|
|
||||||
|
|
||||||
anetSockName(fd,ip,sizeof(ip),&port);
|
|
||||||
return anetFormatAddr(fmt, fmt_len, ip, port);
|
|
||||||
}
|
|
||||||
|
18
src/anet.h
18
src/anet.h
@ -53,6 +53,10 @@ extern "C" {
|
|||||||
#undef ip_len
|
#undef ip_len
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* FD to address string conversion types */
|
||||||
|
#define FD_TO_PEER_NAME 0
|
||||||
|
#define FD_TO_SOCK_NAME 1
|
||||||
|
|
||||||
int anetTcpConnect(char *err, const char *addr, int port);
|
int anetTcpConnect(char *err, const char *addr, int port);
|
||||||
int anetTcpNonBlockConnect(char *err, const char *addr, int port);
|
int anetTcpNonBlockConnect(char *err, const char *addr, int port);
|
||||||
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||||
@ -60,27 +64,25 @@ int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
|
|||||||
int anetUnixConnect(char *err, const char *path);
|
int anetUnixConnect(char *err, const char *path);
|
||||||
int anetUnixNonBlockConnect(char *err, const char *path);
|
int anetUnixNonBlockConnect(char *err, const char *path);
|
||||||
int anetRead(int fd, char *buf, int count);
|
int anetRead(int fd, char *buf, int count);
|
||||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags);
|
||||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
int anetTcpServer(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
int anetTcp6Server(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
|
||||||
int anetUnixServer(char *err, char *path, mode_t perm, 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 anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||||
int anetUnixAccept(char *err, int serversock);
|
int anetUnixAccept(char *err, int serversock);
|
||||||
int anetWrite(int fd, char *buf, int count);
|
int anetWrite(int fd, char *buf, int count);
|
||||||
int anetNonBlock(char *err, int fd);
|
int anetNonBlock(char *err, int fd);
|
||||||
int anetBlock(char *err, int fd);
|
int anetBlock(char *err, int fd);
|
||||||
|
int anetCloexec(int fd);
|
||||||
int anetEnableTcpNoDelay(char *err, int fd);
|
int anetEnableTcpNoDelay(char *err, int fd);
|
||||||
int anetDisableTcpNoDelay(char *err, int fd);
|
int anetDisableTcpNoDelay(char *err, int fd);
|
||||||
int anetTcpKeepAlive(char *err, int fd);
|
int anetTcpKeepAlive(char *err, int fd);
|
||||||
int anetSendTimeout(char *err, int fd, long long ms);
|
int anetSendTimeout(char *err, int fd, long long ms);
|
||||||
int anetRecvTimeout(char *err, int fd, long long ms);
|
int anetRecvTimeout(char *err, int fd, long long ms);
|
||||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
|
int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type);
|
||||||
int anetKeepAlive(char *err, int fd, int interval);
|
int anetKeepAlive(char *err, int fd, int interval);
|
||||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
|
|
||||||
int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port);
|
int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port);
|
||||||
int anetFormatPeer(int fd, char *fmt, size_t fmt_len);
|
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type);
|
||||||
int anetFormatSock(int fd, char *fmt, size_t fmt_len);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
327
src/aof.cpp
327
src/aof.cpp
@ -221,29 +221,27 @@ int aofFsyncInProgress(void) {
|
|||||||
/* Starts a background task that performs fsync() against the specified
|
/* Starts a background task that performs fsync() against the specified
|
||||||
* file descriptor (the one of the AOF file) in another thread. */
|
* file descriptor (the one of the AOF file) in another thread. */
|
||||||
void aof_background_fsync(int fd) {
|
void aof_background_fsync(int fd) {
|
||||||
bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
|
bioCreateFsyncJob(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kills an AOFRW child process if exists */
|
/* Kills an AOFRW child process if exists */
|
||||||
void killAppendOnlyChild(void) {
|
void killAppendOnlyChild(void) {
|
||||||
int statloc;
|
int statloc;
|
||||||
/* No AOFRW child? return. */
|
/* No AOFRW child? return. */
|
||||||
if (g_pserver->aof_child_pid == -1) return;
|
if (g_pserver->child_type != CHILD_TYPE_AOF) return;
|
||||||
/* Kill AOFRW child, wait for child exit. */
|
/* Kill AOFRW child, wait for child exit. */
|
||||||
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
||||||
(long) g_pserver->aof_child_pid);
|
(long) g_pserver->child_pid);
|
||||||
if (kill(g_pserver->aof_child_pid,SIGUSR1) != -1) {
|
if (kill(g_pserver->child_pid,SIGUSR1) != -1) {
|
||||||
while(wait3(&statloc,0,NULL) != g_pserver->aof_child_pid);
|
while(wait3(&statloc,0,NULL) != g_pserver->child_pid);
|
||||||
}
|
}
|
||||||
/* Reset the buffer accumulating changes while the child saves. */
|
/* Reset the buffer accumulating changes while the child saves. */
|
||||||
aofRewriteBufferReset();
|
aofRewriteBufferReset();
|
||||||
aofRemoveTempFile(g_pserver->aof_child_pid);
|
aofRemoveTempFile(g_pserver->child_pid);
|
||||||
g_pserver->aof_child_pid = -1;
|
resetChildState();
|
||||||
g_pserver->aof_rewrite_time_start = -1;
|
g_pserver->aof_rewrite_time_start = -1;
|
||||||
/* Close pipes used for IPC between the two processes. */
|
/* Close pipes used for IPC between the two processes. */
|
||||||
aofClosePipes();
|
aofClosePipes();
|
||||||
closeChildInfoPipe();
|
|
||||||
updateDictResizePolicy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||||
@ -252,6 +250,8 @@ void stopAppendOnly(void) {
|
|||||||
serverAssert(g_pserver->aof_state != AOF_OFF);
|
serverAssert(g_pserver->aof_state != AOF_OFF);
|
||||||
flushAppendOnlyFile(1);
|
flushAppendOnlyFile(1);
|
||||||
redis_fsync(g_pserver->aof_fd);
|
redis_fsync(g_pserver->aof_fd);
|
||||||
|
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||||
|
g_pserver->aof_last_fsync = g_pserver->unixtime;
|
||||||
close(g_pserver->aof_fd);
|
close(g_pserver->aof_fd);
|
||||||
|
|
||||||
g_pserver->aof_fd = -1;
|
g_pserver->aof_fd = -1;
|
||||||
@ -259,6 +259,8 @@ void stopAppendOnly(void) {
|
|||||||
g_pserver->aof_state = AOF_OFF;
|
g_pserver->aof_state = AOF_OFF;
|
||||||
g_pserver->aof_rewrite_scheduled = 0;
|
g_pserver->aof_rewrite_scheduled = 0;
|
||||||
killAppendOnlyChild();
|
killAppendOnlyChild();
|
||||||
|
sdsfree(g_pserver->aof_buf);
|
||||||
|
g_pserver->aof_buf = sdsempty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called when the user switches from "appendonly no" to "appendonly yes"
|
/* Called when the user switches from "appendonly no" to "appendonly yes"
|
||||||
@ -280,14 +282,14 @@ int startAppendOnly(void) {
|
|||||||
strerror(errno));
|
strerror(errno));
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
if (hasActiveChildProcess() && g_pserver->aof_child_pid == -1) {
|
if (hasActiveChildProcess() && g_pserver->child_type != CHILD_TYPE_AOF) {
|
||||||
g_pserver->aof_rewrite_scheduled = 1;
|
g_pserver->aof_rewrite_scheduled = 1;
|
||||||
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
||||||
} else {
|
} else {
|
||||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
/* 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
|
* start a new one: the old one cannot be reused because it is not
|
||||||
* accumulating the AOF buffer. */
|
* accumulating the AOF buffer. */
|
||||||
if (g_pserver->aof_child_pid != -1) {
|
if (g_pserver->child_type == CHILD_TYPE_AOF) {
|
||||||
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
|
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
|
||||||
killAppendOnlyChild();
|
killAppendOnlyChild();
|
||||||
}
|
}
|
||||||
@ -302,6 +304,12 @@ int startAppendOnly(void) {
|
|||||||
g_pserver->aof_state = AOF_WAIT_REWRITE;
|
g_pserver->aof_state = AOF_WAIT_REWRITE;
|
||||||
g_pserver->aof_last_fsync = g_pserver->unixtime;
|
g_pserver->aof_last_fsync = g_pserver->unixtime;
|
||||||
g_pserver->aof_fd = newfd;
|
g_pserver->aof_fd = newfd;
|
||||||
|
|
||||||
|
/* If AOF was in error state, we just ignore it and log the event. */
|
||||||
|
if (g_pserver->aof_last_write_status == C_ERR) {
|
||||||
|
serverLog(LL_WARNING,"AOF reopen, just ignore the last error.");
|
||||||
|
g_pserver->aof_last_write_status = C_OK;
|
||||||
|
}
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,10 +479,11 @@ void flushAppendOnlyFile(int force) {
|
|||||||
|
|
||||||
/* Handle the AOF write error. */
|
/* Handle the AOF write error. */
|
||||||
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
|
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||||
/* We can't recover when the fsync policy is ALWAYS since the
|
/* We can't recover when the fsync policy is ALWAYS since the reply
|
||||||
* reply for the client is already in the output buffers, and we
|
* for the client is already in the output buffers (both writes and
|
||||||
* have the contract with the user that on acknowledged write data
|
* reads), and the changes to the db can't be rolled back. Since we
|
||||||
* is synced on disk. */
|
* 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...");
|
serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
|
||||||
exit(1);
|
exit(1);
|
||||||
} else {
|
} else {
|
||||||
@ -522,7 +531,14 @@ try_fsync:
|
|||||||
/* redis_fsync is defined as fdatasync() for Linux in order to avoid
|
/* redis_fsync is defined as fdatasync() for Linux in order to avoid
|
||||||
* flushing metadata. */
|
* flushing metadata. */
|
||||||
latencyStartMonitor(latency);
|
latencyStartMonitor(latency);
|
||||||
redis_fsync(g_pserver->aof_fd); /* Let's try to get this data on the disk */
|
/* 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(g_pserver->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);
|
latencyEndMonitor(latency);
|
||||||
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
||||||
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||||
@ -594,11 +610,10 @@ sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, r
|
|||||||
}
|
}
|
||||||
decrRefCount(seconds);
|
decrRefCount(seconds);
|
||||||
|
|
||||||
argv[0] = createStringObject("PEXPIREAT",9);
|
argv[0] = shared.pexpireat;
|
||||||
argv[1] = key;
|
argv[1] = key;
|
||||||
argv[2] = createStringObjectFromLongLong(when);
|
argv[2] = createStringObjectFromLongLong(when);
|
||||||
buf = catAppendOnlyGenericCommand(buf, 3, argv);
|
buf = catAppendOnlyGenericCommand(buf, 3, argv);
|
||||||
decrRefCount(argv[0]);
|
|
||||||
decrRefCount(argv[2]);
|
decrRefCount(argv[2]);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
@ -654,42 +669,39 @@ sds catAppendOnlyExpireMemberAtCommand(sds buf, struct redisCommand *cmd, robj *
|
|||||||
|
|
||||||
sds catCommandForAofAndActiveReplication(sds buf, struct redisCommand *cmd, robj **argv, int argc)
|
sds catCommandForAofAndActiveReplication(sds buf, struct redisCommand *cmd, robj **argv, int argc)
|
||||||
{
|
{
|
||||||
robj *tmpargv[3];
|
|
||||||
|
|
||||||
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
|
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
|
||||||
cmd->proc == expireatCommand) {
|
cmd->proc == expireatCommand) {
|
||||||
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
|
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
|
||||||
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
|
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
|
||||||
} else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
|
|
||||||
/* Translate SETEX/PSETEX to SET and PEXPIREAT */
|
|
||||||
tmpargv[0] = createStringObject("SET",3);
|
|
||||||
tmpargv[1] = argv[1];
|
|
||||||
tmpargv[2] = argv[3];
|
|
||||||
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
|
|
||||||
decrRefCount(tmpargv[0]);
|
|
||||||
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
|
|
||||||
} else if (cmd->proc == setCommand && argc > 3) {
|
} else if (cmd->proc == setCommand && argc > 3) {
|
||||||
int i;
|
robj *pxarg = NULL;
|
||||||
robj *exarg = NULL, *pxarg = NULL;
|
/* When SET is used with EX/PX argument setGenericCommand propagates them with PX millisecond argument.
|
||||||
for (i = 3; i < argc; i ++) {
|
* So since the command arguments are re-written there, we can rely here on the index of PX being 3. */
|
||||||
if (!strcasecmp(szFromObj(argv[i]), "ex")) exarg = argv[i+1];
|
if (!strcasecmp(szFromObj(argv[3]), "px")) {
|
||||||
if (!strcasecmp(szFromObj(argv[i]), "px")) pxarg = argv[i+1];
|
pxarg = argv[4];
|
||||||
}
|
}
|
||||||
serverAssert(!(exarg && pxarg));
|
/* For AOF we convert SET key value relative time in milliseconds to SET key value absolute time in
|
||||||
if (exarg || pxarg) {
|
* millisecond. Whenever the condition is true it implies that original SET has been transformed
|
||||||
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */
|
* to SET PX with millisecond time argument so we do not need to worry about unit here.*/
|
||||||
buf = catAppendOnlyGenericCommand(buf,3,argv);
|
if (pxarg) {
|
||||||
if (exarg)
|
robj *millisecond = getDecodedObject(pxarg);
|
||||||
buf = catAppendOnlyExpireAtCommand(buf,cserver.expireCommand,argv[1],
|
long long when = strtoll(szFromObj(millisecond),NULL,10);
|
||||||
exarg);
|
when += mstime();
|
||||||
if (pxarg)
|
|
||||||
buf = catAppendOnlyExpireAtCommand(buf,cserver.pexpireCommand,argv[1],
|
decrRefCount(millisecond);
|
||||||
pxarg);
|
|
||||||
|
robj *newargs[5];
|
||||||
|
newargs[0] = argv[0];
|
||||||
|
newargs[1] = argv[1];
|
||||||
|
newargs[2] = argv[2];
|
||||||
|
newargs[3] = shared.pxat;
|
||||||
|
newargs[4] = createStringObjectFromLongLong(when);
|
||||||
|
buf = catAppendOnlyGenericCommand(buf,5,newargs);
|
||||||
|
decrRefCount(newargs[4]);
|
||||||
} else {
|
} else {
|
||||||
buf = catAppendOnlyGenericCommand(buf,argc,argv);
|
buf = catAppendOnlyGenericCommand(buf,argc,argv);
|
||||||
}
|
}
|
||||||
} else if (cmd->proc == expireMemberCommand || cmd->proc == expireMemberAtCommand ||
|
} else if (cmd->proc == expireMemberCommand || cmd->proc == expireMemberAtCommand || cmd->proc == pexpireMemberAtCommand) {
|
||||||
cmd->proc == pexpireMemberAtCommand) {
|
|
||||||
/* Translate subkey expire commands to PEXPIREMEMBERAT */
|
/* Translate subkey expire commands to PEXPIREMEMBERAT */
|
||||||
buf = catAppendOnlyExpireMemberAtCommand(buf, cmd, argv, argc);
|
buf = catAppendOnlyExpireMemberAtCommand(buf, cmd, argv, argc);
|
||||||
} else {
|
} else {
|
||||||
@ -728,7 +740,7 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
|||||||
* accumulate the differences between the child DB and the current one
|
* accumulate the differences between the child DB and the current one
|
||||||
* in a buffer, so that when the child process will do its work we
|
* in a buffer, so that when the child process will do its work we
|
||||||
* can append the differences to the new append only file. */
|
* can append the differences to the new append only file. */
|
||||||
if (g_pserver->aof_child_pid != -1)
|
if (g_pserver->child_type == CHILD_TYPE_AOF)
|
||||||
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
|
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
|
||||||
|
|
||||||
sdsfree(buf);
|
sdsfree(buf);
|
||||||
@ -752,11 +764,24 @@ struct client *createAOFClient(void) {
|
|||||||
c->querybuf_peak = 0;
|
c->querybuf_peak = 0;
|
||||||
c->argc = 0;
|
c->argc = 0;
|
||||||
c->argv = NULL;
|
c->argv = NULL;
|
||||||
|
c->original_argc = 0;
|
||||||
|
c->original_argv = NULL;
|
||||||
c->argv_len_sum = 0;
|
c->argv_len_sum = 0;
|
||||||
c->bufpos = 0;
|
c->bufpos = 0;
|
||||||
c->flags = 0;
|
|
||||||
c->fPendingAsyncWrite = FALSE;
|
c->fPendingAsyncWrite = FALSE;
|
||||||
c->fPendingAsyncWriteHandler = FALSE;
|
c->fPendingAsyncWriteHandler = FALSE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
c->btype = BLOCKED_NONE;
|
c->btype = BLOCKED_NONE;
|
||||||
/* We set the fake client as a replica waiting for the synchronization
|
/* We set the fake client as a replica waiting for the synchronization
|
||||||
* so that Redis will not try to send replies to this client. */
|
* so that Redis will not try to send replies to this client. */
|
||||||
@ -766,8 +791,9 @@ struct client *createAOFClient(void) {
|
|||||||
c->obuf_soft_limit_reached_time = 0;
|
c->obuf_soft_limit_reached_time = 0;
|
||||||
c->watched_keys = listCreate();
|
c->watched_keys = listCreate();
|
||||||
c->peerid = NULL;
|
c->peerid = NULL;
|
||||||
|
c->sockname = NULL;
|
||||||
c->resp = 2;
|
c->resp = 2;
|
||||||
c->puser = NULL;
|
c->user = NULL;
|
||||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||||
fastlock_init(&c->lock, "fake client");
|
fastlock_init(&c->lock, "fake client");
|
||||||
@ -790,6 +816,7 @@ void freeFakeClient(struct client *c) {
|
|||||||
listRelease(c->reply);
|
listRelease(c->reply);
|
||||||
listRelease(c->watched_keys);
|
listRelease(c->watched_keys);
|
||||||
freeClientMultiState(c);
|
freeClientMultiState(c);
|
||||||
|
freeClientOriginalArgv(c);
|
||||||
fastlock_unlock(&c->lock);
|
fastlock_unlock(&c->lock);
|
||||||
fastlock_free(&c->lock);
|
fastlock_free(&c->lock);
|
||||||
zfree(c);
|
zfree(c);
|
||||||
@ -951,7 +978,7 @@ int loadAppendOnlyFile(char *filename) {
|
|||||||
fakeClient->cmd = NULL;
|
fakeClient->cmd = NULL;
|
||||||
if (g_pserver->aof_load_truncated) valid_up_to = ftello(fp);
|
if (g_pserver->aof_load_truncated) valid_up_to = ftello(fp);
|
||||||
if (g_pserver->key_load_delay)
|
if (g_pserver->key_load_delay)
|
||||||
usleep(g_pserver->key_load_delay);
|
debugDelay(g_pserver->key_load_delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This point can only be reached when EOF is reached without errors.
|
/* This point can only be reached when EOF is reached without errors.
|
||||||
@ -1052,15 +1079,25 @@ int rewriteListObject(rio *r, robj *key, robj *o) {
|
|||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||||
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
|
if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
|
||||||
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
|
!rioWriteBulkString(r,"RPUSH",5) ||
|
||||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
!rioWriteBulkObject(r,key))
|
||||||
|
{
|
||||||
|
quicklistReleaseIterator(li);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.value) {
|
if (entry.value) {
|
||||||
if (rioWriteBulkString(r,(char*)entry.value,entry.sz) == 0) return 0;
|
if (!rioWriteBulkString(r,(char*)entry.value,entry.sz)) {
|
||||||
|
quicklistReleaseIterator(li);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (rioWriteBulkLongLong(r,entry.longval) == 0) return 0;
|
if (!rioWriteBulkLongLong(r,entry.longval)) {
|
||||||
|
quicklistReleaseIterator(li);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||||
items--;
|
items--;
|
||||||
@ -1086,11 +1123,14 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
|
|||||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||||
|
|
||||||
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
|
if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
|
||||||
if (rioWriteBulkString(r,"SADD",4) == 0) return 0;
|
!rioWriteBulkString(r,"SADD",4) ||
|
||||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
!rioWriteBulkObject(r,key))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
if (rioWriteBulkLongLong(r,llval) == 0) return 0;
|
}
|
||||||
|
if (!rioWriteBulkLongLong(r,llval)) return 0;
|
||||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||||
items--;
|
items--;
|
||||||
}
|
}
|
||||||
@ -1104,11 +1144,18 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
|
|||||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||||
|
|
||||||
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
|
if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
|
||||||
if (rioWriteBulkString(r,"SADD",4) == 0) return 0;
|
!rioWriteBulkString(r,"SADD",4) ||
|
||||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
!rioWriteBulkObject(r,key))
|
||||||
|
{
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!rioWriteBulkString(r,ele,sdslen(ele))) {
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0;
|
|
||||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||||
items--;
|
items--;
|
||||||
}
|
}
|
||||||
@ -1145,15 +1192,18 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
|||||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||||
|
|
||||||
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
|
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
|
||||||
if (rioWriteBulkString(r,"ZADD",4) == 0) return 0;
|
!rioWriteBulkString(r,"ZADD",4) ||
|
||||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
!rioWriteBulkObject(r,key))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
if (rioWriteBulkDouble(r,score) == 0) return 0;
|
}
|
||||||
|
if (!rioWriteBulkDouble(r,score)) return 0;
|
||||||
if (vstr != NULL) {
|
if (vstr != NULL) {
|
||||||
if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0;
|
if (!rioWriteBulkString(r,(char*)vstr,vlen)) return 0;
|
||||||
} else {
|
} else {
|
||||||
if (rioWriteBulkLongLong(r,vll) == 0) return 0;
|
if (!rioWriteBulkLongLong(r,vll)) return 0;
|
||||||
}
|
}
|
||||||
zzlNext(zl,&eptr,&sptr);
|
zzlNext(zl,&eptr,&sptr);
|
||||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||||
@ -1172,12 +1222,20 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
|||||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||||
|
|
||||||
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
|
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
|
||||||
if (rioWriteBulkString(r,"ZADD",4) == 0) return 0;
|
!rioWriteBulkString(r,"ZADD",4) ||
|
||||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
!rioWriteBulkObject(r,key))
|
||||||
|
{
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!rioWriteBulkDouble(r,*score) ||
|
||||||
|
!rioWriteBulkString(r,ele,sdslen(ele)))
|
||||||
|
{
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
if (rioWriteBulkDouble(r,*score) == 0) return 0;
|
|
||||||
if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0;
|
|
||||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||||
items--;
|
items--;
|
||||||
}
|
}
|
||||||
@ -1226,13 +1284,21 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
|
|||||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||||
|
|
||||||
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
|
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
|
||||||
if (rioWriteBulkString(r,"HMSET",5) == 0) return 0;
|
!rioWriteBulkString(r,"HMSET",5) ||
|
||||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
!rioWriteBulkObject(r,key))
|
||||||
|
{
|
||||||
|
hashTypeReleaseIterator(hi);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) == 0) return 0;
|
if (!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) ||
|
||||||
if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE) == 0) return 0;
|
!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE))
|
||||||
|
{
|
||||||
|
hashTypeReleaseIterator(hi);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||||
items--;
|
items--;
|
||||||
}
|
}
|
||||||
@ -1278,6 +1344,20 @@ int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t
|
|||||||
return 1;
|
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 <key> <group> <consumer> */
|
||||||
|
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.
|
/* Emit the commands needed to rebuild a stream object.
|
||||||
* The function returns 0 on error, 1 on success. */
|
* The function returns 0 on error, 1 on success. */
|
||||||
int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||||
@ -1366,13 +1446,25 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Generate XCLAIMs for each consumer that happens to
|
/* Generate XCLAIMs for each consumer that happens to
|
||||||
* have pending entries. Empty consumers have no semantical
|
* have pending entries. Empty consumers would be generated with
|
||||||
* value so they are discarded. */
|
* XGROUP CREATECONSUMER. */
|
||||||
raxIterator ri_cons;
|
raxIterator ri_cons;
|
||||||
raxStart(&ri_cons,group->consumers);
|
raxStart(&ri_cons,group->consumers);
|
||||||
raxSeek(&ri_cons,"^",NULL,0);
|
raxSeek(&ri_cons,"^",NULL,0);
|
||||||
while(raxNext(&ri_cons)) {
|
while(raxNext(&ri_cons)) {
|
||||||
streamConsumer *consumer = (streamConsumer*)ri_cons.data;
|
streamConsumer *consumer = (streamConsumer*)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
|
/* For the current consumer, iterate all the PEL entries
|
||||||
* to emit the XCLAIM protocol. */
|
* to emit the XCLAIM protocol. */
|
||||||
raxIterator ri_pel;
|
raxIterator ri_pel;
|
||||||
@ -1438,6 +1530,8 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
|||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
size_t processed = 0;
|
size_t processed = 0;
|
||||||
int j;
|
int j;
|
||||||
|
long key_count = 0;
|
||||||
|
long long updated_time = 0;
|
||||||
|
|
||||||
for (j = 0; j < cserver.dbnum; j++) {
|
for (j = 0; j < cserver.dbnum; j++) {
|
||||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||||
@ -1509,6 +1603,17 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
|||||||
processed = aof->processed_bytes;
|
processed = aof->processed_bytes;
|
||||||
aofReadDiffFromParent();
|
aofReadDiffFromParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
di = NULL;
|
di = NULL;
|
||||||
@ -1535,6 +1640,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
|||||||
int nodata = 0;
|
int nodata = 0;
|
||||||
mstime_t start = 0;
|
mstime_t start = 0;
|
||||||
|
|
||||||
|
{ // BEGIN GOTO SCOPED VARIABLES
|
||||||
/* Note that we have to use a different temp name here compared to the
|
/* Note that we have to use a different temp name here compared to the
|
||||||
* one used by rewriteAppendOnlyFileBackground() function. */
|
* one used by rewriteAppendOnlyFileBackground() function. */
|
||||||
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
|
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
|
||||||
@ -1603,9 +1709,32 @@ int rewriteAppendOnlyFile(char *filename) {
|
|||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"Concatenating %.2f MB of AOF diff received from parent.",
|
"Concatenating %.2f MB of AOF diff received from parent.",
|
||||||
(double) sdslen(g_pserver->aof_child_diff) / (1024*1024));
|
(double) sdslen(g_pserver->aof_child_diff) / (1024*1024));
|
||||||
if (rioWrite(&aof,g_pserver->aof_child_diff,sdslen(g_pserver->aof_child_diff)) == 0)
|
|
||||||
|
/* Now we write the entire AOF buffer we received from the parent
|
||||||
|
* via the pipe during the life of this fork child.
|
||||||
|
* once a second, we'll take a break and send updated COW info to the parent */
|
||||||
|
size_t bytes_to_write = sdslen(g_pserver->aof_child_diff);
|
||||||
|
const char *buf = g_pserver->aof_child_diff;
|
||||||
|
long long cow_updated_time = mstime();
|
||||||
|
long long key_count = dbTotalServerKeyCount();
|
||||||
|
while (bytes_to_write) {
|
||||||
|
/* We write the AOF buffer in chunk of 8MB so that we can check the time in between them */
|
||||||
|
size_t chunk_size = bytes_to_write < (8<<20) ? bytes_to_write : (8<<20);
|
||||||
|
|
||||||
|
if (rioWrite(&aof,buf,chunk_size) == 0)
|
||||||
goto werr;
|
goto werr;
|
||||||
|
|
||||||
|
bytes_to_write -= chunk_size;
|
||||||
|
buf += chunk_size;
|
||||||
|
|
||||||
|
/* Update COW info */
|
||||||
|
long long now = mstime();
|
||||||
|
if (now - cow_updated_time >= 1000) {
|
||||||
|
sendChildInfo(CHILD_INFO_TYPE_CURRENT_INFO, key_count, "AOF rewrite");
|
||||||
|
cow_updated_time = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Make sure data will not remain on the OS's output buffers */
|
/* Make sure data will not remain on the OS's output buffers */
|
||||||
if (fflush(fp)) goto werr;
|
if (fflush(fp)) goto werr;
|
||||||
if (fsync(fileno(fp))) goto werr;
|
if (fsync(fileno(fp))) goto werr;
|
||||||
@ -1623,6 +1752,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
|||||||
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
||||||
stopSaving(1);
|
stopSaving(1);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
|
} // END GOTO SCOPED VARIABLES
|
||||||
|
|
||||||
werr:
|
werr:
|
||||||
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||||
@ -1739,7 +1869,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
|
|
||||||
if (hasActiveChildProcess()) return C_ERR;
|
if (hasActiveChildProcess()) return C_ERR;
|
||||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||||
openChildInfoPipe();
|
|
||||||
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
|
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
|
||||||
char tmpfile[256];
|
char tmpfile[256];
|
||||||
|
|
||||||
@ -1748,7 +1877,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist);
|
redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist);
|
||||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||||
sendChildCOWInfo(CHILD_TYPE_AOF, "AOF rewrite");
|
sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");
|
||||||
exitFromChild(0);
|
exitFromChild(0);
|
||||||
} else {
|
} else {
|
||||||
exitFromChild(1);
|
exitFromChild(1);
|
||||||
@ -1756,7 +1885,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
if (childpid == -1) {
|
if (childpid == -1) {
|
||||||
closeChildInfoPipe();
|
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Can't rewrite append only file in background: fork: %s",
|
"Can't rewrite append only file in background: fork: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
@ -1764,10 +1892,9 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"Background append only file rewriting started by pid %d",childpid);
|
"Background append only file rewriting started by pid %ld",(long)childpid);
|
||||||
g_pserver->aof_rewrite_scheduled = 0;
|
g_pserver->aof_rewrite_scheduled = 0;
|
||||||
g_pserver->aof_rewrite_time_start = time(NULL);
|
g_pserver->aof_rewrite_time_start = time(NULL);
|
||||||
g_pserver->aof_child_pid = childpid;
|
|
||||||
updateDictResizePolicy();
|
updateDictResizePolicy();
|
||||||
/* We set appendseldb to -1 in order to force the next call to the
|
/* We set appendseldb to -1 in order to force the next call to the
|
||||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||||
@ -1781,7 +1908,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void bgrewriteaofCommand(client *c) {
|
void bgrewriteaofCommand(client *c) {
|
||||||
if (g_pserver->aof_child_pid != -1) {
|
if (g_pserver->child_type == CHILD_TYPE_AOF) {
|
||||||
addReplyError(c,"Background append only file rewriting already in progress");
|
addReplyError(c,"Background append only file rewriting already in progress");
|
||||||
} else if (hasActiveChildProcess()) {
|
} else if (hasActiveChildProcess()) {
|
||||||
g_pserver->aof_rewrite_scheduled = 1;
|
g_pserver->aof_rewrite_scheduled = 1;
|
||||||
@ -1839,7 +1966,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
* rewritten AOF. */
|
* rewritten AOF. */
|
||||||
latencyStartMonitor(latency);
|
latencyStartMonitor(latency);
|
||||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
|
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
|
||||||
(int)g_pserver->aof_child_pid);
|
(int)g_pserver->child_pid);
|
||||||
newfd = open(tmpfile,O_WRONLY|O_APPEND);
|
newfd = open(tmpfile,O_WRONLY|O_APPEND);
|
||||||
if (newfd == -1) {
|
if (newfd == -1) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
@ -1856,6 +1983,20 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
latencyEndMonitor(latency);
|
latencyEndMonitor(latency);
|
||||||
latencyAddSampleIfNeeded("aof-rewrite-diff-write",latency);
|
latencyAddSampleIfNeeded("aof-rewrite-diff-write",latency);
|
||||||
|
|
||||||
|
if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC) {
|
||||||
|
aof_background_fsync(newfd);
|
||||||
|
} else if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||||
|
latencyStartMonitor(latency);
|
||||||
|
if (redis_fsync(newfd) == -1) {
|
||||||
|
serverLog(LL_WARNING,
|
||||||
|
"Error trying to fsync the parent diff to the rewritten AOF: %s", strerror(errno));
|
||||||
|
close(newfd);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
latencyEndMonitor(latency);
|
||||||
|
latencyAddSampleIfNeeded("aof-rewrite-done-fsync",latency);
|
||||||
|
}
|
||||||
|
|
||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"Residual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024*1024));
|
"Residual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024*1024));
|
||||||
|
|
||||||
@ -1922,14 +2063,11 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
/* AOF enabled, replace the old fd with the new one. */
|
/* AOF enabled, replace the old fd with the new one. */
|
||||||
oldfd = g_pserver->aof_fd;
|
oldfd = g_pserver->aof_fd;
|
||||||
g_pserver->aof_fd = newfd;
|
g_pserver->aof_fd = newfd;
|
||||||
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS)
|
|
||||||
redis_fsync(newfd);
|
|
||||||
else if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC)
|
|
||||||
aof_background_fsync(newfd);
|
|
||||||
g_pserver->aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
g_pserver->aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||||
aofUpdateCurrentSize();
|
aofUpdateCurrentSize();
|
||||||
g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size;
|
g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size;
|
||||||
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||||
|
g_pserver->aof_last_fsync = g_pserver->unixtime;
|
||||||
|
|
||||||
/* Clear regular AOF buffer since its contents was just written to
|
/* Clear regular AOF buffer since its contents was just written to
|
||||||
* the new AOF from the background rewrite buffer. */
|
* the new AOF from the background rewrite buffer. */
|
||||||
@ -1945,7 +2083,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
g_pserver->aof_state = AOF_ON;
|
g_pserver->aof_state = AOF_ON;
|
||||||
|
|
||||||
/* Asynchronously close the overwritten AOF. */
|
/* Asynchronously close the overwritten AOF. */
|
||||||
if (oldfd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL);
|
if (oldfd != -1) bioCreateCloseJob(oldfd);
|
||||||
|
|
||||||
serverLog(LL_VERBOSE,
|
serverLog(LL_VERBOSE,
|
||||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||||
@ -1967,8 +2105,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
cleanup:
|
cleanup:
|
||||||
aofClosePipes();
|
aofClosePipes();
|
||||||
aofRewriteBufferReset();
|
aofRewriteBufferReset();
|
||||||
aofRemoveTempFile(g_pserver->aof_child_pid);
|
aofRemoveTempFile(g_pserver->child_pid);
|
||||||
g_pserver->aof_child_pid = -1;
|
|
||||||
g_pserver->aof_rewrite_time_last = time(NULL)-g_pserver->aof_rewrite_time_start;
|
g_pserver->aof_rewrite_time_last = time(NULL)-g_pserver->aof_rewrite_time_start;
|
||||||
g_pserver->aof_rewrite_time_start = -1;
|
g_pserver->aof_rewrite_time_start = -1;
|
||||||
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
|
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
|
||||||
|
109
src/atomicvar.h
109
src/atomicvar.h
@ -1,5 +1,5 @@
|
|||||||
/* This file implements atomic counters using __atomic or __sync macros if
|
/* This file implements atomic counters using c11 _Atomic, __atomic or __sync
|
||||||
* available, otherwise synchronizing different threads using a mutex.
|
* macros if available, otherwise we will throw an error when compile.
|
||||||
*
|
*
|
||||||
* The exported interface is composed of three macros:
|
* The exported interface is composed of three macros:
|
||||||
*
|
*
|
||||||
@ -8,16 +8,8 @@
|
|||||||
* atomicDecr(var,count) -- Decrement the atomic counter
|
* atomicDecr(var,count) -- Decrement the atomic counter
|
||||||
* atomicGet(var,dstvar) -- Fetch the atomic counter value
|
* atomicGet(var,dstvar) -- Fetch the atomic counter value
|
||||||
* atomicSet(var,value) -- Set the atomic counter value
|
* atomicSet(var,value) -- Set the atomic counter value
|
||||||
*
|
* atomicGetWithSync(var,value) -- 'atomicGet' with inter-thread synchronization
|
||||||
* The variable 'var' should also have a declared mutex with the same
|
* atomicSetWithSync(var,value) -- 'atomicSet' with inter-thread synchronization
|
||||||
* name and the "_mutex" postfix, for instance:
|
|
||||||
*
|
|
||||||
* long myvar;
|
|
||||||
* pthread_mutex_t myvar_mutex;
|
|
||||||
* atomicSet(myvar,12345);
|
|
||||||
*
|
|
||||||
* If atomic primitives are available (tested in config.h) the mutex
|
|
||||||
* is not used.
|
|
||||||
*
|
*
|
||||||
* Never use return value from the macros, instead use the AtomicGetIncr()
|
* Never use return value from the macros, instead use the AtomicGetIncr()
|
||||||
* if you need to get the current value and increment it atomically, like
|
* if you need to get the current value and increment it atomically, like
|
||||||
@ -58,17 +50,64 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
#ifndef __ATOMIC_VAR_H
|
#ifndef __ATOMIC_VAR_H
|
||||||
#define __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
|
/* 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
|
* 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
|
* by Helgrind (even if they are less efficient) so that no false positive
|
||||||
* is reported. */
|
* is reported. */
|
||||||
// #define __ATOMIC_VAR_FORCE_SYNC_MACROS
|
// #define __ATOMIC_VAR_FORCE_SYNC_MACROS
|
||||||
|
|
||||||
#if !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && defined(__ATOMIC_RELAXED) && !defined(__sun) && (!defined(__clang__) || !defined(__APPLE__) || __apple_build_version__ > 4210057)
|
/* 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 <valgrind/helgrind.h>
|
||||||
|
#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 <stdatomic.h>
|
||||||
|
#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 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 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. */
|
/* Implementation using __atomic macros. */
|
||||||
|
|
||||||
#define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED)
|
#define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED)
|
||||||
@ -80,6 +119,11 @@
|
|||||||
dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \
|
dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \
|
||||||
} while(0)
|
} while(0)
|
||||||
#define atomicSet(var,value) __atomic_store_n(&var,value,__ATOMIC_RELAXED)
|
#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 REDIS_ATOMIC_API "atomic-builtin"
|
#define REDIS_ATOMIC_API "atomic-builtin"
|
||||||
|
|
||||||
#elif defined(HAVE_ATOMIC)
|
#elif defined(HAVE_ATOMIC)
|
||||||
@ -96,38 +140,19 @@
|
|||||||
#define atomicSet(var,value) do { \
|
#define atomicSet(var,value) do { \
|
||||||
while(!__sync_bool_compare_and_swap(&var,var,value)); \
|
while(!__sync_bool_compare_and_swap(&var,var,value)); \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
/* Actually the builtin issues a full memory barrier by default. */
|
||||||
|
#define atomicGetWithSync(var,dstvar) { \
|
||||||
|
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 REDIS_ATOMIC_API "sync-builtin"
|
#define REDIS_ATOMIC_API "sync-builtin"
|
||||||
|
|
||||||
#else
|
#else
|
||||||
/* Implementation using pthread mutex. */
|
#error "Unable to determine atomic operations for your platform"
|
||||||
|
|
||||||
#define atomicIncr(var,count) do { \
|
|
||||||
pthread_mutex_lock(&var ## _mutex); \
|
|
||||||
var += (count); \
|
|
||||||
pthread_mutex_unlock(&var ## _mutex); \
|
|
||||||
} while(0)
|
|
||||||
#define atomicGetIncr(var,oldvalue_var,count) do { \
|
|
||||||
pthread_mutex_lock(&var ## _mutex); \
|
|
||||||
oldvalue_var = var; \
|
|
||||||
var += (count); \
|
|
||||||
pthread_mutex_unlock(&var ## _mutex); \
|
|
||||||
} while(0)
|
|
||||||
#define atomicDecr(var,count) do { \
|
|
||||||
pthread_mutex_lock(&var ## _mutex); \
|
|
||||||
var -= (count); \
|
|
||||||
pthread_mutex_unlock(&var ## _mutex); \
|
|
||||||
} while(0)
|
|
||||||
#define atomicGet(var,dstvar) do { \
|
|
||||||
pthread_mutex_lock(&var ## _mutex); \
|
|
||||||
dstvar = var; \
|
|
||||||
pthread_mutex_unlock(&var ## _mutex); \
|
|
||||||
} while(0)
|
|
||||||
#define atomicSet(var,value) do { \
|
|
||||||
pthread_mutex_lock(&var ## _mutex); \
|
|
||||||
var = value; \
|
|
||||||
pthread_mutex_unlock(&var ## _mutex); \
|
|
||||||
} while(0)
|
|
||||||
#define REDIS_ATOMIC_API "pthread-mutex"
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#endif /* __ATOMIC_VAR_H */
|
#endif /* __ATOMIC_VAR_H */
|
||||||
|
61
src/bio.cpp
61
src/bio.cpp
@ -78,15 +78,13 @@ static unsigned long long bio_pending[BIO_NUM_OPS];
|
|||||||
* file as the API does not expose the internals at all. */
|
* file as the API does not expose the internals at all. */
|
||||||
struct bio_job {
|
struct bio_job {
|
||||||
time_t time; /* Time at which the job was created. */
|
time_t time; /* Time at which the job was created. */
|
||||||
/* Job specific arguments pointers. If we need to pass more than three
|
/* Job specific arguments.*/
|
||||||
* arguments we can just pass a pointer to a structure or alike. */
|
int fd; /* Fd for file based background jobs */
|
||||||
void *arg1, *arg2, *arg3;
|
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 */
|
||||||
};
|
};
|
||||||
|
|
||||||
void *bioProcessBackgroundJobs(void *arg);
|
void *bioProcessBackgroundJobs(void *arg);
|
||||||
void lazyfreeFreeObjectFromBioThread(robj *o);
|
|
||||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, expireset *set);
|
|
||||||
void lazyfreeFreeSlotsMapFromBioThread(rax *rt);
|
|
||||||
|
|
||||||
/* Make sure we have enough stack to perform all the things we do in the
|
/* Make sure we have enough stack to perform all the things we do in the
|
||||||
* main thread. */
|
* main thread. */
|
||||||
@ -128,13 +126,8 @@ void bioInit(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
|
void bioSubmitJob(int type, struct bio_job *job) {
|
||||||
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job), MALLOC_LOCAL);
|
|
||||||
|
|
||||||
job->time = time(NULL);
|
job->time = time(NULL);
|
||||||
job->arg1 = arg1;
|
|
||||||
job->arg2 = arg2;
|
|
||||||
job->arg3 = arg3;
|
|
||||||
pthread_mutex_lock(&bio_mutex[type]);
|
pthread_mutex_lock(&bio_mutex[type]);
|
||||||
listAddNodeTail(bio_jobs[type],job);
|
listAddNodeTail(bio_jobs[type],job);
|
||||||
bio_pending[type]++;
|
bio_pending[type]++;
|
||||||
@ -142,6 +135,35 @@ void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
|
|||||||
pthread_mutex_unlock(&bio_mutex[type]);
|
pthread_mutex_unlock(&bio_mutex[type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
|
||||||
|
va_list valist;
|
||||||
|
/* Allocate memory for the job structure and all required
|
||||||
|
* arguments */
|
||||||
|
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job) + sizeof(void *) * (arg_count));
|
||||||
|
job->free_fn = free_fn;
|
||||||
|
|
||||||
|
va_start(valist, arg_count);
|
||||||
|
for (int i = 0; i < arg_count; i++) {
|
||||||
|
job->free_args[i] = va_arg(valist, void *);
|
||||||
|
}
|
||||||
|
va_end(valist);
|
||||||
|
bioSubmitJob(BIO_LAZY_FREE, job);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bioCreateCloseJob(int fd) {
|
||||||
|
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job));
|
||||||
|
job->fd = fd;
|
||||||
|
|
||||||
|
bioSubmitJob(BIO_CLOSE_FILE, job);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bioCreateFsyncJob(int fd) {
|
||||||
|
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job));
|
||||||
|
job->fd = fd;
|
||||||
|
|
||||||
|
bioSubmitJob(BIO_AOF_FSYNC, job);
|
||||||
|
}
|
||||||
|
|
||||||
void *bioProcessBackgroundJobs(void *arg) {
|
void *bioProcessBackgroundJobs(void *arg) {
|
||||||
struct bio_job *job;
|
struct bio_job *job;
|
||||||
unsigned long type = (unsigned long) arg;
|
unsigned long type = (unsigned long) arg;
|
||||||
@ -196,20 +218,11 @@ void *bioProcessBackgroundJobs(void *arg) {
|
|||||||
|
|
||||||
/* Process the job accordingly to its type. */
|
/* Process the job accordingly to its type. */
|
||||||
if (type == BIO_CLOSE_FILE) {
|
if (type == BIO_CLOSE_FILE) {
|
||||||
close((long)job->arg1);
|
close(job->fd);
|
||||||
} else if (type == BIO_AOF_FSYNC) {
|
} else if (type == BIO_AOF_FSYNC) {
|
||||||
redis_fsync((long)job->arg1);
|
redis_fsync(job->fd);
|
||||||
} else if (type == BIO_LAZY_FREE) {
|
} else if (type == BIO_LAZY_FREE) {
|
||||||
/* What we free changes depending on what arguments are set:
|
job->free_fn(job->free_args);
|
||||||
* arg1 -> free the object at pointer.
|
|
||||||
* arg2 & arg3 -> free two dictionaries (a Redis DB).
|
|
||||||
* only arg3 -> free the radix tree. */
|
|
||||||
if (job->arg1)
|
|
||||||
lazyfreeFreeObjectFromBioThread((robj*)job->arg1);
|
|
||||||
else if (job->arg2 && job->arg3)
|
|
||||||
lazyfreeFreeDatabaseFromBioThread((dict*)job->arg2,(expireset*)job->arg3);
|
|
||||||
else if (job->arg3)
|
|
||||||
lazyfreeFreeSlotsMapFromBioThread((rax*)job->arg3);
|
|
||||||
} else {
|
} else {
|
||||||
serverPanic("Wrong job type in bioProcessBackgroundJobs().");
|
serverPanic("Wrong job type in bioProcessBackgroundJobs().");
|
||||||
}
|
}
|
||||||
|
@ -32,13 +32,17 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef void lazy_free_fn(void *args[]);
|
||||||
|
|
||||||
/* Exported API */
|
/* Exported API */
|
||||||
void bioInit(void);
|
void bioInit(void);
|
||||||
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
|
|
||||||
unsigned long long bioPendingJobsOfType(int type);
|
unsigned long long bioPendingJobsOfType(int type);
|
||||||
unsigned long long bioWaitStepOfType(int type);
|
unsigned long long bioWaitStepOfType(int type);
|
||||||
time_t bioOlderJobOfType(int type);
|
time_t bioOlderJobOfType(int type);
|
||||||
void bioKillThreads(void);
|
void bioKillThreads(void);
|
||||||
|
void bioCreateCloseJob(int fd);
|
||||||
|
void bioCreateFsyncJob(int fd);
|
||||||
|
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...);
|
||||||
|
|
||||||
/* Background job opcodes */
|
/* Background job opcodes */
|
||||||
#define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
|
#define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
/* Count number of bits set in the binary array pointed by 's' and long
|
/* Count number of bits set in the binary array pointed by 's' and long
|
||||||
* 'count' bytes. The implementation of this function is required to
|
* 'count' bytes. The implementation of this function is required to
|
||||||
* work with an input string length up to 512 MB. */
|
* work with an input string length up to 512 MB or more (server.proto_max_bulk_len) */
|
||||||
size_t redisPopcount(const void *s, long count) {
|
size_t redisPopcount(const void *s, long count) {
|
||||||
size_t bits = 0;
|
size_t bits = 0;
|
||||||
unsigned char *p = (unsigned char*)s;
|
unsigned char *p = (unsigned char*)s;
|
||||||
@ -407,7 +407,7 @@ void printBits(unsigned char *p, unsigned long count) {
|
|||||||
|
|
||||||
/* This helper function used by GETBIT / SETBIT parses the bit offset argument
|
/* 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
|
* making sure an error is returned if it is negative or if it overflows
|
||||||
* Redis 512 MB limit for the string value.
|
* 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
|
* 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
|
* will also parse bit offsets prefixed by "#". In such a case the offset
|
||||||
@ -430,8 +430,8 @@ int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int b
|
|||||||
/* Adjust the offset by 'bits' for #<offset> form. */
|
/* Adjust the offset by 'bits' for #<offset> form. */
|
||||||
if (usehash) loffset *= bits;
|
if (usehash) loffset *= bits;
|
||||||
|
|
||||||
/* Limit offset to 512MB in bytes */
|
/* Limit offset to server.proto_max_bulk_len (512MB in bytes by default) */
|
||||||
if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
|
if ((loffset < 0) || (loffset >> 3) >= g_pserver->proto_max_bulk_len)
|
||||||
{
|
{
|
||||||
addReplyError(c,err);
|
addReplyError(c,err);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
@ -482,12 +482,12 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
|
|||||||
robj *lookupStringForBitCommand(client *c, size_t maxbit) {
|
robj *lookupStringForBitCommand(client *c, size_t maxbit) {
|
||||||
size_t byte = maxbit >> 3;
|
size_t byte = maxbit >> 3;
|
||||||
robj *o = lookupKeyWrite(c->db,c->argv[1]);
|
robj *o = lookupKeyWrite(c->db,c->argv[1]);
|
||||||
|
if (checkType(c,o,OBJ_STRING)) return NULL;
|
||||||
|
|
||||||
if (o == NULL) {
|
if (o == NULL) {
|
||||||
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
|
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
|
||||||
dbAdd(c->db,c->argv[1],o);
|
dbAdd(c->db,c->argv[1],o);
|
||||||
} else {
|
} else {
|
||||||
if (checkType(c,o,OBJ_STRING)) return NULL;
|
|
||||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||||
o->m_ptr = sdsgrowzero(szFromObj(o),byte+1);
|
o->m_ptr = sdsgrowzero(szFromObj(o),byte+1);
|
||||||
}
|
}
|
||||||
@ -619,7 +619,7 @@ void bitopCommand(client *c) {
|
|||||||
else if (!strcasecmp(opname, "rshift"))
|
else if (!strcasecmp(opname, "rshift"))
|
||||||
op = BITOP_RSHIFT;
|
op = BITOP_RSHIFT;
|
||||||
else {
|
else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -731,7 +731,6 @@ void bitopCommand(client *c) {
|
|||||||
/* Compute the bit operation, if at least one string is not empty. */
|
/* Compute the bit operation, if at least one string is not empty. */
|
||||||
if (maxlen) {
|
if (maxlen) {
|
||||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||||
unsigned char output, byte;
|
|
||||||
unsigned long i;
|
unsigned long i;
|
||||||
|
|
||||||
/* Fast path: as far as we have data for all the input bitmaps we
|
/* Fast path: as far as we have data for all the input bitmaps we
|
||||||
@ -801,24 +800,35 @@ void bitopCommand(client *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* j is set to the next byte to process by the previous loop. */
|
/* j is set to the next byte to process by the previous loop. */
|
||||||
for (; j < maxlen; j++) {
|
for (; j < maxlen; j++) {
|
||||||
output = (len[0] <= j) ? 0 : src[0][j];
|
auto output = (len[0] <= j) ? 0 : src[0][j];
|
||||||
if (op == BITOP_NOT) output = ~output;
|
if (op == BITOP_NOT) output = ~output;
|
||||||
for (i = 1; i < numkeys; i++) {
|
for (unsigned long i = 1; i < numkeys; i++) {
|
||||||
byte = (len[i] <= j) ? 0 : src[i][j];
|
int skip = 0;
|
||||||
|
auto byte = (len[i] <= j) ? 0 : src[i][j];
|
||||||
switch(op) {
|
switch(op) {
|
||||||
case BITOP_AND: output &= byte; break;
|
case BITOP_AND:
|
||||||
case BITOP_OR: output |= byte; break;
|
output &= byte;
|
||||||
|
skip = (output == 0);
|
||||||
|
break;
|
||||||
|
case BITOP_OR:
|
||||||
|
output |= byte;
|
||||||
|
skip = (output == 0xff);
|
||||||
|
break;
|
||||||
case BITOP_XOR: output ^= byte; break;
|
case BITOP_XOR: output ^= byte; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skip) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res[j] = output;
|
res[j] = output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (j = 0; j < numkeys; j++) {
|
for (j = 0; j < numkeys; j++) {
|
||||||
if (objects[j])
|
if (objects[j])
|
||||||
decrRefCount(objects[j]);
|
decrRefCount(objects[j]);
|
||||||
@ -876,7 +886,7 @@ void bitcountCommand(client *c) {
|
|||||||
end = strlen-1;
|
end = strlen-1;
|
||||||
} else {
|
} else {
|
||||||
/* Syntax error. */
|
/* Syntax error. */
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -941,7 +951,7 @@ void bitposCommand(client *c) {
|
|||||||
end = strlen-1;
|
end = strlen-1;
|
||||||
} else {
|
} else {
|
||||||
/* Syntax error. */
|
/* Syntax error. */
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1033,7 +1043,7 @@ void bitfieldGeneric(client *c, int flags) {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
zfree(ops);
|
zfree(ops);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
120
src/blocked.cpp
120
src/blocked.cpp
@ -61,9 +61,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "slowlog.h"
|
||||||
|
#include "latency.h"
|
||||||
|
#include "monotonic.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
|
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int wherefrom, int whereto);
|
||||||
|
int getListPositionFromObjectOrReply(client *c, robj *arg, int *position);
|
||||||
|
|
||||||
/* This structure represents the blocked key information that we store
|
/* This structure represents the blocked key information that we store
|
||||||
* in the client structure. Each client blocked on keys, has a
|
* in the client structure. Each client blocked on keys, has a
|
||||||
@ -90,6 +94,26 @@ void blockClient(client *c, int btype) {
|
|||||||
g_pserver->blocked_clients++;
|
g_pserver->blocked_clients++;
|
||||||
g_pserver->blocked_clients_by_type[btype]++;
|
g_pserver->blocked_clients_by_type[btype]++;
|
||||||
addClientToTimeoutTable(c);
|
addClientToTimeoutTable(c);
|
||||||
|
if (btype == BLOCKED_PAUSE) {
|
||||||
|
listAddNodeTail(g_pserver->paused_clients, c);
|
||||||
|
c->paused_list_node = listLast(g_pserver->paused_clients);
|
||||||
|
/* Mark this client to execute its command */
|
||||||
|
c->flags |= CLIENT_PENDING_COMMAND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function is called after a client has finished a blocking operation
|
||||||
|
* in order to update the total command duration, log the command into
|
||||||
|
* the Slow log if needed, and log the reply duration event if needed. */
|
||||||
|
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us){
|
||||||
|
const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us;
|
||||||
|
c->lastcmd->microseconds += total_cmd_duration;
|
||||||
|
/* Log the command into the Slow log if needed. */
|
||||||
|
if (!(c->lastcmd->flags & CMD_SKIP_SLOWLOG)) {
|
||||||
|
slowlogPushEntryIfNeeded(c,c->argv,c->argc,total_cmd_duration);
|
||||||
|
/* Log the reply duration event. */
|
||||||
|
latencyAddSampleIfNeeded("command-unblocking",reply_us/1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called in the beforeSleep() function of the event loop
|
/* This function is called in the beforeSleep() function of the event loop
|
||||||
@ -118,6 +142,11 @@ void processUnblockedClients(int iel) {
|
|||||||
* client is not blocked before to proceed, but things may change and
|
* client is not blocked before to proceed, but things may change and
|
||||||
* the code is conceptually more correct this way. */
|
* the code is conceptually more correct this way. */
|
||||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||||
|
/* If we have a queued command, execute it now. */
|
||||||
|
if (processPendingCommandsAndResetClient(c, CMD_CALL_FULL) == C_ERR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Then process client if it has more data in it's buffer. */
|
||||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||||
processInputBuffer(c, CMD_CALL_FULL);
|
processInputBuffer(c, CMD_CALL_FULL);
|
||||||
}
|
}
|
||||||
@ -168,6 +197,9 @@ void unblockClient(client *c) {
|
|||||||
} else if (c->btype == BLOCKED_MODULE) {
|
} else if (c->btype == BLOCKED_MODULE) {
|
||||||
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
|
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
|
||||||
unblockClientFromModule(c);
|
unblockClientFromModule(c);
|
||||||
|
} else if (c->btype == BLOCKED_PAUSE) {
|
||||||
|
listDelNode(g_pserver->paused_clients,c->paused_list_node);
|
||||||
|
c->paused_list_node = NULL;
|
||||||
} else {
|
} else {
|
||||||
serverPanic("Unknown btype in unblockClient().");
|
serverPanic("Unknown btype in unblockClient().");
|
||||||
}
|
}
|
||||||
@ -216,9 +248,16 @@ void disconnectAllBlockedClients(void) {
|
|||||||
|
|
||||||
fastlock_lock(&c->lock);
|
fastlock_lock(&c->lock);
|
||||||
if (c->flags & CLIENT_BLOCKED) {
|
if (c->flags & CLIENT_BLOCKED) {
|
||||||
addReplySds(c,sdsnew(
|
/* PAUSED 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->btype == BLOCKED_PAUSE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
addReplyError(c,
|
||||||
"-UNBLOCKED force unblock from blocking operation, "
|
"-UNBLOCKED force unblock from blocking operation, "
|
||||||
"instance state changed (master -> replica?)\r\n"));
|
"instance state changed (master -> replica?)");
|
||||||
unblockClient(c);
|
unblockClient(c);
|
||||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||||
}
|
}
|
||||||
@ -250,10 +289,9 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
robj *dstkey = receiver->bpop.target;
|
robj *dstkey = receiver->bpop.target;
|
||||||
int where = (receiver->lastcmd &&
|
int wherefrom = receiver->bpop.listpos.wherefrom;
|
||||||
receiver->lastcmd->proc == blpopCommand) ?
|
int whereto = receiver->bpop.listpos.whereto;
|
||||||
LIST_HEAD : LIST_TAIL;
|
robj *value = listTypePop(o, wherefrom);
|
||||||
robj *value = listTypePop(o,where);
|
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
/* Protect receiver->bpop.target, that will be
|
/* Protect receiver->bpop.target, that will be
|
||||||
@ -262,14 +300,17 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
|||||||
if (dstkey) incrRefCount(dstkey);
|
if (dstkey) incrRefCount(dstkey);
|
||||||
unblockClient(receiver);
|
unblockClient(receiver);
|
||||||
|
|
||||||
|
monotime replyTimer;
|
||||||
|
elapsedStart(&replyTimer);
|
||||||
if (serveClientBlockedOnList(receiver,
|
if (serveClientBlockedOnList(receiver,
|
||||||
rl->key,dstkey,rl->db,value,
|
rl->key,dstkey,rl->db,value,
|
||||||
where) == C_ERR)
|
wherefrom, whereto) == C_ERR)
|
||||||
{
|
{
|
||||||
/* If we failed serving the client we need
|
/* If we failed serving the client we need
|
||||||
* to also undo the POP operation. */
|
* to also undo the POP operation. */
|
||||||
listTypePush(o,value,where);
|
listTypePush(o,value,wherefrom);
|
||||||
}
|
}
|
||||||
|
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||||
|
|
||||||
if (dstkey) decrRefCount(dstkey);
|
if (dstkey) decrRefCount(dstkey);
|
||||||
decrRefCount(value);
|
decrRefCount(value);
|
||||||
@ -315,7 +356,10 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
|||||||
receiver->lastcmd->proc == bzpopminCommand)
|
receiver->lastcmd->proc == bzpopminCommand)
|
||||||
? ZSET_MIN : ZSET_MAX;
|
? ZSET_MIN : ZSET_MAX;
|
||||||
unblockClient(receiver);
|
unblockClient(receiver);
|
||||||
|
monotime replyTimer;
|
||||||
|
elapsedStart(&replyTimer);
|
||||||
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
||||||
|
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||||
zcard--;
|
zcard--;
|
||||||
|
|
||||||
/* Replicate the command. */
|
/* Replicate the command. */
|
||||||
@ -392,13 +436,22 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
|||||||
int noack = 0;
|
int noack = 0;
|
||||||
|
|
||||||
if (group) {
|
if (group) {
|
||||||
|
int created = 0;
|
||||||
consumer =
|
consumer =
|
||||||
streamLookupConsumer(group,
|
streamLookupConsumer(group,
|
||||||
szFromObj(receiver->bpop.xread_consumer),
|
szFromObj(receiver->bpop.xread_consumer),
|
||||||
SLC_NONE);
|
SLC_NONE,
|
||||||
|
&created);
|
||||||
noack = receiver->bpop.xread_group_noack;
|
noack = receiver->bpop.xread_group_noack;
|
||||||
|
if (created && noack) {
|
||||||
|
streamPropagateConsumerCreation(receiver,rl->key,
|
||||||
|
receiver->bpop.xread_group,
|
||||||
|
consumer->name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
monotime replyTimer;
|
||||||
|
elapsedStart(&replyTimer);
|
||||||
/* Emit the two elements sub-array consisting of
|
/* Emit the two elements sub-array consisting of
|
||||||
* the name of the stream and the data we
|
* the name of the stream and the data we
|
||||||
* extracted from it. Wrapped in a single-item
|
* extracted from it. Wrapped in a single-item
|
||||||
@ -418,6 +471,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
|||||||
streamReplyWithRange(receiver,s,&start,NULL,
|
streamReplyWithRange(receiver,s,&start,NULL,
|
||||||
receiver->bpop.xread_count,
|
receiver->bpop.xread_count,
|
||||||
0, group, consumer, noack, &pi);
|
0, group, consumer, noack, &pi);
|
||||||
|
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||||
|
|
||||||
/* Note that after we unblock the client, 'gt'
|
/* Note that after we unblock the client, 'gt'
|
||||||
* and other receiver->bpop stuff are no longer
|
* and other receiver->bpop stuff are no longer
|
||||||
@ -464,7 +518,10 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
|||||||
* different modules with different triggers to consider if a key
|
* different modules with different triggers to consider if a key
|
||||||
* is ready or not. This means we can't exit the loop but need
|
* is ready or not. This means we can't exit the loop but need
|
||||||
* to continue after the first failure. */
|
* to continue after the first failure. */
|
||||||
|
monotime replyTimer;
|
||||||
|
elapsedStart(&replyTimer);
|
||||||
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
|
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
|
||||||
|
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||||
|
|
||||||
moduleUnblockClient(receiver);
|
moduleUnblockClient(receiver);
|
||||||
}
|
}
|
||||||
@ -480,8 +537,8 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
|||||||
* one new element via some write operation are accumulated into
|
* one new element via some write operation are accumulated into
|
||||||
* the g_pserver->ready_keys list. This function will run the list and will
|
* the g_pserver->ready_keys list. This function will run the list and will
|
||||||
* serve clients accordingly. Note that the function will iterate again and
|
* serve clients accordingly. Note that the function will iterate again and
|
||||||
* again as a result of serving BRPOPLPUSH we can have new blocking clients
|
* again as a result of serving BLMOVE we can have new blocking clients
|
||||||
* to serve because of the PUSH side of BRPOPLPUSH.
|
* to serve because of the PUSH side of BLMOVE.
|
||||||
*
|
*
|
||||||
* This function is normally "fair", that is, it will server clients
|
* This function is normally "fair", that is, it will server clients
|
||||||
* using a FIFO behavior. However this fairness is violated in certain
|
* using a FIFO behavior. However this fairness is violated in certain
|
||||||
@ -515,7 +572,7 @@ void handleClientsBlockedOnKeys(void) {
|
|||||||
/* Even if we are not inside call(), increment the call depth
|
/* Even if we are not inside call(), increment the call depth
|
||||||
* in order to make sure that keys are expired against a fixed
|
* in order to make sure that keys are expired against a fixed
|
||||||
* reference time, and not against the wallclock time. This
|
* reference time, and not against the wallclock time. This
|
||||||
* way we can lookup an object multiple times (BRPOPLPUSH does
|
* way we can lookup an object multiple times (BLMOVE does
|
||||||
* that) without the risk of it being freed in the second
|
* that) without the risk of it being freed in the second
|
||||||
* lookup, invalidating the first one.
|
* lookup, invalidating the first one.
|
||||||
* See https://github.com/antirez/redis/pull/6554. */
|
* See https://github.com/antirez/redis/pull/6554. */
|
||||||
@ -575,7 +632,7 @@ void handleClientsBlockedOnKeys(void) {
|
|||||||
* stream keys, we also provide an array of streamID structures: clients will
|
* stream keys, we also provide an array of streamID structures: clients will
|
||||||
* be unblocked only when items with an ID greater or equal to the specified
|
* be unblocked only when items with an ID greater or equal to the specified
|
||||||
* one is appended to the stream. */
|
* one is appended to the stream. */
|
||||||
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids) {
|
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, struct listPos *listpos, streamID *ids) {
|
||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
list *l;
|
list *l;
|
||||||
int j;
|
int j;
|
||||||
@ -583,6 +640,8 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
|||||||
c->bpop.timeout = timeout;
|
c->bpop.timeout = timeout;
|
||||||
c->bpop.target = target;
|
c->bpop.target = target;
|
||||||
|
|
||||||
|
if (listpos != NULL) c->bpop.listpos = *listpos;
|
||||||
|
|
||||||
if (target != NULL) incrRefCount(target);
|
if (target != NULL) incrRefCount(target);
|
||||||
|
|
||||||
for (j = 0; j < numkeys; j++) {
|
for (j = 0; j < numkeys; j++) {
|
||||||
@ -656,6 +715,16 @@ void unblockClientWaitingData(client *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int 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
|
/* If the specified key has clients blocked waiting for list pushes, this
|
||||||
* function will put the key reference into the g_pserver->ready_keys list.
|
* function will put the key reference into the g_pserver->ready_keys list.
|
||||||
* Note that db->ready_keys is a hash table that allows us to avoid putting
|
* Note that db->ready_keys is a hash table that allows us to avoid putting
|
||||||
@ -663,9 +732,24 @@ void unblockClientWaitingData(client *c) {
|
|||||||
* made by a script or in the context of MULTI/EXEC.
|
* made by a script or in the context of MULTI/EXEC.
|
||||||
*
|
*
|
||||||
* The list will be finally processed by handleClientsBlockedOnKeys() */
|
* The list will be finally processed by handleClientsBlockedOnKeys() */
|
||||||
void signalKeyAsReady(redisDb *db, robj *key) {
|
void signalKeyAsReady(redisDb *db, robj *key, int type) {
|
||||||
readyList *rl;
|
readyList *rl;
|
||||||
|
|
||||||
|
/* Quick returns. */
|
||||||
|
int btype = getBlockedTypeByType(type);
|
||||||
|
if (btype == BLOCKED_NONE) {
|
||||||
|
/* The type can never block. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!g_pserver->blocked_clients_by_type[btype] &&
|
||||||
|
!g_pserver->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;
|
||||||
|
}
|
||||||
|
|
||||||
/* No clients blocking for this key? No need to queue it. */
|
/* No clients blocking for this key? No need to queue it. */
|
||||||
if (dictFind(db->blocking_keys,key) == NULL) return;
|
if (dictFind(db->blocking_keys,key) == NULL) return;
|
||||||
|
|
||||||
@ -693,4 +777,8 @@ void signalKeyAsReady(redisDb *db, robj *key) {
|
|||||||
serverAssert(dictAdd(db->ready_keys,key,NULL) == DICT_OK);
|
serverAssert(dictAdd(db->ready_keys,key,NULL) == DICT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void signalKeyAsReady(redisDb *db, sds key, int type) {
|
||||||
|
redisObjectStack o;
|
||||||
|
initStaticStringObject(o, key);
|
||||||
|
signalKeyAsReady(db, &o, type);
|
||||||
|
}
|
@ -30,18 +30,25 @@
|
|||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t keys;
|
||||||
|
size_t cow;
|
||||||
|
double progress;
|
||||||
|
childInfoType information_type; /* Type of information */
|
||||||
|
} child_info_data;
|
||||||
|
|
||||||
/* Open a child-parent channel used in order to move information about the
|
/* 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
|
* RDB / AOF saving process from the child to the parent (for instance
|
||||||
* the amount of copy on write memory used) */
|
* the amount of copy on write memory used) */
|
||||||
void openChildInfoPipe(void) {
|
void openChildInfoPipe(void) {
|
||||||
if (pipe(g_pserver->child_info_pipe) == -1) {
|
if (pipe(g_pserver->child_info_pipe) == -1) {
|
||||||
/* On error our two file descriptors should be still set to -1,
|
/* On error our two file descriptors should be still set to -1,
|
||||||
* but we call anyway cloesChildInfoPipe() since can't hurt. */
|
* but we call anyway closeChildInfoPipe() since can't hurt. */
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
} else if (anetNonBlock(NULL,g_pserver->child_info_pipe[0]) != ANET_OK) {
|
} else if (anetNonBlock(NULL,g_pserver->child_info_pipe[0]) != ANET_OK) {
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
} else {
|
} else {
|
||||||
memset(&g_pserver->child_info_data,0,sizeof(g_pserver->child_info_data));
|
g_pserver->child_info_nread = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,34 +61,88 @@ void closeChildInfoPipe(void) {
|
|||||||
close(g_pserver->child_info_pipe[1]);
|
close(g_pserver->child_info_pipe[1]);
|
||||||
g_pserver->child_info_pipe[0] = -1;
|
g_pserver->child_info_pipe[0] = -1;
|
||||||
g_pserver->child_info_pipe[1] = -1;
|
g_pserver->child_info_pipe[1] = -1;
|
||||||
|
g_pserver->child_info_nread = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send COW data to parent. The child should call this function after populating
|
/* Send save data to parent. */
|
||||||
* the corresponding fields it want to sent (according to the process type). */
|
void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress, const char *pname) {
|
||||||
void sendChildInfo(int ptype) {
|
|
||||||
if (g_pserver->child_info_pipe[1] == -1) return;
|
if (g_pserver->child_info_pipe[1] == -1) return;
|
||||||
g_pserver->child_info_data.magic = CHILD_INFO_MAGIC;
|
|
||||||
g_pserver->child_info_data.process_type = ptype;
|
child_info_data data = {0}; /* zero everything, including padding to sattisfy valgrind */
|
||||||
ssize_t wlen = sizeof(g_pserver->child_info_data);
|
data.information_type = info_type;
|
||||||
if (write(g_pserver->child_info_pipe[1],&g_pserver->child_info_data,wlen) != wlen) {
|
data.keys = keys;
|
||||||
|
data.cow = zmalloc_get_private_dirty(-1);
|
||||||
|
data.progress = progress;
|
||||||
|
|
||||||
|
if (data.cow) {
|
||||||
|
serverLog((info_type == CHILD_INFO_TYPE_CURRENT_INFO) ? LL_VERBOSE : LL_NOTICE,
|
||||||
|
"%s: %zu MB of memory used by copy-on-write",
|
||||||
|
pname, data.cow/(1024*1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t wlen = sizeof(data);
|
||||||
|
|
||||||
|
if (write(g_pserver->child_info_pipe[1], &data, wlen) != wlen) {
|
||||||
/* Nothing to do on error, this will be detected by the other side. */
|
/* Nothing to do on error, this will be detected by the other side. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Receive COW data from parent. */
|
/* Update Child info. */
|
||||||
|
void updateChildInfo(childInfoType information_type, size_t cow, size_t keys, double progress) {
|
||||||
|
if (information_type == CHILD_INFO_TYPE_CURRENT_INFO) {
|
||||||
|
g_pserver->stat_current_cow_bytes = cow;
|
||||||
|
g_pserver->stat_current_save_keys_processed = keys;
|
||||||
|
if (progress != -1) g_pserver->stat_module_progress = progress;
|
||||||
|
} else if (information_type == CHILD_INFO_TYPE_AOF_COW_SIZE) {
|
||||||
|
g_pserver->stat_aof_cow_bytes = cow;
|
||||||
|
} else if (information_type == CHILD_INFO_TYPE_RDB_COW_SIZE) {
|
||||||
|
g_pserver->stat_rdb_cow_bytes = cow;
|
||||||
|
} else if (information_type == CHILD_INFO_TYPE_MODULE_COW_SIZE) {
|
||||||
|
g_pserver->stat_module_cow_bytes = cow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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, 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 (g_pserver->child_info_nread == wlen) g_pserver->child_info_nread = 0;
|
||||||
|
|
||||||
|
int nread = read(g_pserver->child_info_pipe[0], (char *)&buffer + g_pserver->child_info_nread, wlen - g_pserver->child_info_nread);
|
||||||
|
if (nread > 0) {
|
||||||
|
g_pserver->child_info_nread += nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We have complete child info */
|
||||||
|
if (g_pserver->child_info_nread == wlen) {
|
||||||
|
*information_type = buffer.information_type;
|
||||||
|
*cow = buffer.cow;
|
||||||
|
*keys = buffer.keys;
|
||||||
|
*progress = buffer.progress;
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Receive info data from child. */
|
||||||
void receiveChildInfo(void) {
|
void receiveChildInfo(void) {
|
||||||
if (g_pserver->child_info_pipe[0] == -1) return;
|
if (g_pserver->child_info_pipe[0] == -1) return;
|
||||||
ssize_t wlen = sizeof(g_pserver->child_info_data);
|
|
||||||
if (read(g_pserver->child_info_pipe[0],&g_pserver->child_info_data,wlen) == wlen &&
|
size_t cow;
|
||||||
g_pserver->child_info_data.magic == CHILD_INFO_MAGIC)
|
size_t keys;
|
||||||
{
|
double progress;
|
||||||
if (g_pserver->child_info_data.process_type == CHILD_TYPE_RDB) {
|
childInfoType information_type;
|
||||||
g_pserver->stat_rdb_cow_bytes = g_pserver->child_info_data.cow_size;
|
|
||||||
} else if (g_pserver->child_info_data.process_type == CHILD_TYPE_AOF) {
|
/* Drain the pipe and update child info so that we get the final message. */
|
||||||
g_pserver->stat_aof_cow_bytes = g_pserver->child_info_data.cow_size;
|
while (readChildInfo(&information_type, &cow, &keys, &progress)) {
|
||||||
} else if (g_pserver->child_info_data.process_type == CHILD_TYPE_MODULE) {
|
updateChildInfo(information_type, cow, keys, progress);
|
||||||
g_pserver->stat_module_cow_bytes = g_pserver->child_info_data.cow_size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
194
src/cli_common.c
Normal file
194
src/cli_common.c
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/* CLI (command line interface) common methods
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, 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 "cli_common.h"
|
||||||
|
#include <errno.h>
|
||||||
|
#include <hiredis.h>
|
||||||
|
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
|
||||||
|
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <hiredis_ssl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
* writen.
|
||||||
|
*/
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
ERR_load_crypto_strings();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
SSL_library_init();
|
||||||
|
#endif
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
58
src/cli_common.h
Normal file
58
src/cli_common.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#ifndef __CLICOMMON_H
|
||||||
|
#define __CLICOMMON_H
|
||||||
|
|
||||||
|
#include <hiredis.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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;
|
||||||
|
/* Prefered cipher list, or NULL (applies only to <= TLSv1.2) */
|
||||||
|
char* ciphers;
|
||||||
|
/* Prefered ciphersuites list, or NULL (applies only to TLSv1.3) */
|
||||||
|
char* ciphersuites;
|
||||||
|
} cliSSLconfig;
|
||||||
|
|
||||||
|
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
|
||||||
|
* not building with TLS support.
|
||||||
|
*/
|
||||||
|
int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
/* Wrapper around OpenSSL (libssl and libcrypto) initialisation.
|
||||||
|
*/
|
||||||
|
int cliSecureInit();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __CLICOMMON_H */
|
282
src/cluster.cpp
282
src/cluster.cpp
@ -47,7 +47,7 @@
|
|||||||
clusterNode *myself = NULL;
|
clusterNode *myself = NULL;
|
||||||
|
|
||||||
clusterNode *createClusterNode(char *nodename, int flags);
|
clusterNode *createClusterNode(char *nodename, int flags);
|
||||||
int clusterAddNode(clusterNode *node);
|
void clusterAddNode(clusterNode *node);
|
||||||
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||||
void clusterReadHandler(connection *conn);
|
void clusterReadHandler(connection *conn);
|
||||||
void clusterSendPing(clusterLink *link, int type);
|
void clusterSendPing(clusterLink *link, int type);
|
||||||
@ -415,7 +415,7 @@ int clusterLockConfig(char *filename) {
|
|||||||
/* To lock it, we need to open the file in a way it is created if
|
/* 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
|
* it does not exist, otherwise there is a race condition with other
|
||||||
* processes. */
|
* processes. */
|
||||||
int fd = open(filename,O_WRONLY|O_CREAT,0644);
|
int fd = open(filename,O_WRONLY|O_CREAT|O_CLOEXEC,0644);
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Can't open %s in order to acquire a lock: %s",
|
"Can't open %s in order to acquire a lock: %s",
|
||||||
@ -519,7 +519,7 @@ void clusterInit(void) {
|
|||||||
if (saveconf) clusterSaveConfigOrDie(1);
|
if (saveconf) clusterSaveConfigOrDie(1);
|
||||||
|
|
||||||
/* We need a listening TCP port for our cluster messaging needs. */
|
/* We need a listening TCP port for our cluster messaging needs. */
|
||||||
g_pserver->cfd_count = 0;
|
g_pserver->cfd.count = 0;
|
||||||
|
|
||||||
/* Port sanity check II
|
/* Port sanity check II
|
||||||
* The other handshake port check is triggered too late to stop
|
* The other handshake port check is triggered too late to stop
|
||||||
@ -529,23 +529,16 @@ void clusterInit(void) {
|
|||||||
serverLog(LL_WARNING, "KeyDB port number too high. "
|
serverLog(LL_WARNING, "KeyDB port number too high. "
|
||||||
"Cluster communication port is 10,000 port "
|
"Cluster communication port is 10,000 port "
|
||||||
"numbers higher than your KeyDB port. "
|
"numbers higher than your KeyDB port. "
|
||||||
"Your KeyDB port number must be "
|
"Your KeyDB port number must be 55535 or less.");
|
||||||
"lower than 55535.");
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (listenToPort(port+CLUSTER_PORT_INCR,
|
if (listenToPort(port+CLUSTER_PORT_INCR, &g_pserver->cfd, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR) {
|
||||||
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR)
|
|
||||||
{
|
|
||||||
exit(1);
|
exit(1);
|
||||||
} else {
|
}
|
||||||
int j;
|
|
||||||
|
|
||||||
for (j = 0; j < g_pserver->cfd_count; j++) {
|
serverAssert(serverTL == &g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN]);
|
||||||
if (aeCreateFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->cfd[j], AE_READABLE,
|
if (createSocketAcceptHandler(&g_pserver->cfd, clusterAcceptHandler) != C_OK) {
|
||||||
clusterAcceptHandler, NULL) == AE_ERR)
|
serverPanic("Unrecoverable error creating Redis Cluster socket accept handler.");
|
||||||
serverPanic("Unrecoverable error creating Redis Cluster "
|
|
||||||
"file event.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The slots -> keys map is a radix tree. Initialize it here. */
|
/* The slots -> keys map is a radix tree. Initialize it here. */
|
||||||
@ -815,6 +808,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
|
|||||||
node->configEpoch = 0;
|
node->configEpoch = 0;
|
||||||
node->flags = flags;
|
node->flags = flags;
|
||||||
memset(node->slots,0,sizeof(node->slots));
|
memset(node->slots,0,sizeof(node->slots));
|
||||||
|
node->slots_info = NULL;
|
||||||
node->numslots = 0;
|
node->numslots = 0;
|
||||||
node->numslaves = 0;
|
node->numslaves = 0;
|
||||||
node->slaves = NULL;
|
node->slaves = NULL;
|
||||||
@ -997,12 +991,12 @@ void freeClusterNode(clusterNode *n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Add a node to the nodes hash table */
|
/* Add a node to the nodes hash table */
|
||||||
int clusterAddNode(clusterNode *node) {
|
void clusterAddNode(clusterNode *node) {
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
retval = dictAdd(g_pserver->cluster->nodes,
|
retval = dictAdd(g_pserver->cluster->nodes,
|
||||||
sdsnewlen(node->name,CLUSTER_NAMELEN), node);
|
sdsnewlen(node->name,CLUSTER_NAMELEN), node);
|
||||||
return (retval == DICT_OK) ? C_OK : C_ERR;
|
serverAssert(retval == DICT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove a node from the cluster. The function performs the high level
|
/* Remove a node from the cluster. The function performs the high level
|
||||||
@ -1862,6 +1856,7 @@ int clusterProcessPacket(clusterLink *link) {
|
|||||||
g_pserver->cluster->mf_master_offset == 0)
|
g_pserver->cluster->mf_master_offset == 0)
|
||||||
{
|
{
|
||||||
g_pserver->cluster->mf_master_offset = sender->repl_offset;
|
g_pserver->cluster->mf_master_offset = sender->repl_offset;
|
||||||
|
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER);
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Received replication offset for paused "
|
"Received replication offset for paused "
|
||||||
"master manual failover: %lld",
|
"master manual failover: %lld",
|
||||||
@ -2165,7 +2160,7 @@ int clusterProcessPacket(clusterLink *link) {
|
|||||||
/* Don't bother creating useless objects if there are no
|
/* Don't bother creating useless objects if there are no
|
||||||
* Pub/Sub subscribers. */
|
* Pub/Sub subscribers. */
|
||||||
if (dictSize(g_pserver->pubsub_channels) ||
|
if (dictSize(g_pserver->pubsub_channels) ||
|
||||||
listLength(g_pserver->pubsub_patterns))
|
dictSize(g_pserver->pubsub_patterns))
|
||||||
{
|
{
|
||||||
channel_len = ntohl(hdr->data.publish.msg.channel_len);
|
channel_len = ntohl(hdr->data.publish.msg.channel_len);
|
||||||
message_len = ntohl(hdr->data.publish.msg.message_len);
|
message_len = ntohl(hdr->data.publish.msg.message_len);
|
||||||
@ -2203,9 +2198,15 @@ int clusterProcessPacket(clusterLink *link) {
|
|||||||
resetManualFailover();
|
resetManualFailover();
|
||||||
g_pserver->cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
g_pserver->cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
||||||
g_pserver->cluster->mf_slave = sender;
|
g_pserver->cluster->mf_slave = sender;
|
||||||
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT));
|
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT),CLIENT_PAUSE_WRITE);
|
||||||
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
||||||
sender->name);
|
sender->name);
|
||||||
|
/* 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) {
|
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
|
||||||
clusterNode *n; /* The node the update is about. */
|
clusterNode *n; /* The node the update is about. */
|
||||||
uint64_t reportedConfigEpoch =
|
uint64_t reportedConfigEpoch =
|
||||||
@ -2856,7 +2857,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
|
|||||||
* SLAVE node specific functions
|
* SLAVE node specific functions
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* This function sends a FAILOVE_AUTH_REQUEST message to every node in order to
|
/* 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
|
* see if there is the quorum for this slave instance to failover its failing
|
||||||
* master.
|
* master.
|
||||||
*
|
*
|
||||||
@ -3462,9 +3463,8 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
|||||||
* The function can be used both to initialize the manual failover state at
|
* The function can be used both to initialize the manual failover state at
|
||||||
* startup or to abort a manual failover in progress. */
|
* startup or to abort a manual failover in progress. */
|
||||||
void resetManualFailover(void) {
|
void resetManualFailover(void) {
|
||||||
if (g_pserver->cluster->mf_end && clientsArePaused()) {
|
if (g_pserver->cluster->mf_end) {
|
||||||
g_pserver->clients_pause_end_time = 0;
|
checkClientPauseTimeoutAndReturnIfPaused();
|
||||||
unpauseClientsIfNecessary();
|
|
||||||
}
|
}
|
||||||
g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */
|
g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */
|
||||||
g_pserver->cluster->mf_can_start = 0;
|
g_pserver->cluster->mf_can_start = 0;
|
||||||
@ -3499,7 +3499,10 @@ void clusterHandleManualFailover(void) {
|
|||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"All master replication stream processed, "
|
"All master replication stream processed, "
|
||||||
"manual failover can start.");
|
"manual failover can start.");
|
||||||
|
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------------
|
/* -----------------------------------------------------------------------------
|
||||||
@ -3774,25 +3777,35 @@ void clusterCron(void) {
|
|||||||
* handlers, or to perform potentially expansive tasks that we need to do
|
* handlers, or to perform potentially expansive tasks that we need to do
|
||||||
* a single time before replying to clients. */
|
* a single time before replying to clients. */
|
||||||
void clusterBeforeSleep(void) {
|
void clusterBeforeSleep(void) {
|
||||||
/* Handle failover, this is needed when it is likely that there is already
|
int flags = g_pserver->cluster->todo_before_sleep;
|
||||||
* the quorum from masters in order to react fast. */
|
|
||||||
if (g_pserver->cluster->todo_before_sleep & CLUSTER_TODO_HANDLE_FAILOVER)
|
|
||||||
clusterHandleSlaveFailover();
|
|
||||||
|
|
||||||
/* Update the cluster state. */
|
|
||||||
if (g_pserver->cluster->todo_before_sleep & CLUSTER_TODO_UPDATE_STATE)
|
|
||||||
clusterUpdateState();
|
|
||||||
|
|
||||||
/* Save the config, possibly using fsync. */
|
|
||||||
if (g_pserver->cluster->todo_before_sleep & CLUSTER_TODO_SAVE_CONFIG) {
|
|
||||||
int fsync = g_pserver->cluster->todo_before_sleep &
|
|
||||||
CLUSTER_TODO_FSYNC_CONFIG;
|
|
||||||
clusterSaveConfigOrDie(fsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset our flags (not strictly needed since every single function
|
/* Reset our flags (not strictly needed since every single function
|
||||||
* called for flags set should be able to clear its flag). */
|
* called for flags set should be able to clear its flag). */
|
||||||
g_pserver->cluster->todo_before_sleep = 0;
|
g_pserver->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 (!(g_pserver->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) {
|
void clusterDoBeforeSleep(int flags) {
|
||||||
@ -4173,8 +4186,8 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
|||||||
sds ci;
|
sds ci;
|
||||||
|
|
||||||
/* Node coordinates */
|
/* Node coordinates */
|
||||||
ci = sdscatprintf(sdsempty(),"%.40s %s:%d@%d ",
|
ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
|
||||||
node->name,
|
ci = sdscatfmt(ci," %s:%i@%i ",
|
||||||
node->ip,
|
node->ip,
|
||||||
node->port,
|
node->port,
|
||||||
node->cport);
|
node->cport);
|
||||||
@ -4183,24 +4196,29 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
|||||||
ci = representClusterNodeFlags(ci, node->flags);
|
ci = representClusterNodeFlags(ci, node->flags);
|
||||||
|
|
||||||
/* Slave of... or just "-" */
|
/* Slave of... or just "-" */
|
||||||
|
ci = sdscatlen(ci," ",1);
|
||||||
if (node->slaveof)
|
if (node->slaveof)
|
||||||
ci = sdscatprintf(ci," %.40s ",node->slaveof->name);
|
ci = sdscatlen(ci,node->slaveof->name,CLUSTER_NAMELEN);
|
||||||
else
|
else
|
||||||
ci = sdscatlen(ci," - ",3);
|
ci = sdscatlen(ci,"-",1);
|
||||||
|
|
||||||
unsigned long long nodeEpoch = node->configEpoch;
|
unsigned long long nodeEpoch = node->configEpoch;
|
||||||
if (nodeIsSlave(node) && node->slaveof) {
|
if (nodeIsSlave(node) && node->slaveof) {
|
||||||
nodeEpoch = node->slaveof->configEpoch;
|
nodeEpoch = node->slaveof->configEpoch;
|
||||||
}
|
}
|
||||||
/* Latency from the POV of this node, config epoch, link status */
|
/* Latency from the POV of this node, config epoch, link status */
|
||||||
ci = sdscatprintf(ci,"%lld %lld %llu %s",
|
ci = sdscatfmt(ci," %I %I %U %s",
|
||||||
(long long) node->ping_sent,
|
(long long) node->ping_sent,
|
||||||
(long long) node->pong_received,
|
(long long) node->pong_received,
|
||||||
nodeEpoch,
|
nodeEpoch,
|
||||||
(node->link || node->flags & CLUSTER_NODE_MYSELF) ?
|
(node->link || node->flags & CLUSTER_NODE_MYSELF) ?
|
||||||
"connected" : "disconnected");
|
"connected" : "disconnected");
|
||||||
|
|
||||||
/* Slots served by this instance */
|
/* Slots served by this instance. If we already have slots info,
|
||||||
|
* append it diretly, otherwise, generate slots only if it has. */
|
||||||
|
if (node->slots_info) {
|
||||||
|
ci = sdscatsds(ci, node->slots_info);
|
||||||
|
} else if (node->numslots > 0) {
|
||||||
start = -1;
|
start = -1;
|
||||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||||
int bit;
|
int bit;
|
||||||
@ -4212,13 +4230,14 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
|||||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||||
|
|
||||||
if (start == j-1) {
|
if (start == j-1) {
|
||||||
ci = sdscatprintf(ci," %d",start);
|
ci = sdscatfmt(ci," %i",start);
|
||||||
} else {
|
} else {
|
||||||
ci = sdscatprintf(ci," %d-%d",start,j-1);
|
ci = sdscatfmt(ci," %i-%i",start,j-1);
|
||||||
}
|
}
|
||||||
start = -1;
|
start = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Just for MYSELF node we also dump info about slots that
|
/* Just for MYSELF node we also dump info about slots that
|
||||||
* we are migrating to other instances or importing from other
|
* we are migrating to other instances or importing from other
|
||||||
@ -4237,6 +4256,41 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
|||||||
return ci;
|
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 = g_pserver->cluster->slots[i];
|
||||||
|
start = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate slots info when occur different node with start
|
||||||
|
* or end of slot. */
|
||||||
|
if (i == CLUSTER_SLOTS || n != g_pserver->cluster->slots[i]) {
|
||||||
|
if (!(n->flags & filter)) {
|
||||||
|
if (n->slots_info == NULL) n->slots_info = sdsempty();
|
||||||
|
if (start == i-1) {
|
||||||
|
n->slots_info = sdscatfmt(n->slots_info," %i",start);
|
||||||
|
} else {
|
||||||
|
n->slots_info = sdscatfmt(n->slots_info," %i-%i",start,i-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == CLUSTER_SLOTS) break;
|
||||||
|
n = g_pserver->cluster->slots[i];
|
||||||
|
start = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Generate a csv-alike representation of the nodes we are aware of,
|
/* Generate a csv-alike representation of the nodes we are aware of,
|
||||||
* including the "myself" node, and return an SDS string containing the
|
* including the "myself" node, and return an SDS string containing the
|
||||||
* representation (it is up to the caller to free it).
|
* representation (it is up to the caller to free it).
|
||||||
@ -4254,6 +4308,9 @@ sds clusterGenNodesDescription(int filter) {
|
|||||||
dictIterator *di;
|
dictIterator *di;
|
||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
|
|
||||||
|
/* Generate all nodes slots info firstly. */
|
||||||
|
clusterGenNodesSlotsInfo(filter);
|
||||||
|
|
||||||
di = dictGetSafeIterator(g_pserver->cluster->nodes);
|
di = dictGetSafeIterator(g_pserver->cluster->nodes);
|
||||||
while((de = dictNext(di)) != NULL) {
|
while((de = dictNext(di)) != NULL) {
|
||||||
clusterNode *node = (clusterNode*)dictGetVal(de);
|
clusterNode *node = (clusterNode*)dictGetVal(de);
|
||||||
@ -4263,6 +4320,12 @@ sds clusterGenNodesDescription(int filter) {
|
|||||||
ci = sdscatsds(ci,ni);
|
ci = sdscatsds(ci,ni);
|
||||||
sdsfree(ni);
|
sdsfree(ni);
|
||||||
ci = sdscatlen(ci,"\n",1);
|
ci = sdscatlen(ci,"\n",1);
|
||||||
|
|
||||||
|
/* Release slots info. */
|
||||||
|
if (node->slots_info) {
|
||||||
|
sdsfree(node->slots_info);
|
||||||
|
node->slots_info = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
return ci;
|
return ci;
|
||||||
@ -4385,28 +4448,49 @@ void clusterCommand(client *c) {
|
|||||||
|
|
||||||
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
|
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"ADDSLOTS <slot> [slot ...] -- Assign slots to current node.",
|
"ADDSLOTS <slot> [<slot> ...]",
|
||||||
"BUMPEPOCH -- Advance the cluster config epoch.",
|
" Assign slots to current node.",
|
||||||
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
|
"BUMPEPOCH",
|
||||||
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.",
|
" Advance the cluster config epoch.",
|
||||||
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.",
|
"COUNT-FAILURE-REPORTS <node-id>",
|
||||||
"FAILOVER [force|takeover] -- Promote current replica node to being a master.",
|
" Return number of failure reports for <node-id>.",
|
||||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
"COUNTKEYSINSLOT <slot>",
|
||||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
" Return the number of keys in <slot>.",
|
||||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
"DELSLOTS <slot> [<slot> ...]",
|
||||||
"INFO - Return information about the cluster.",
|
" Delete slots information from current node.",
|
||||||
"KEYSLOT <key> -- Return the hash slot for <key>.",
|
"FAILOVER [FORCE|TAKEOVER]",
|
||||||
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
" Promote current replica node to being a master.",
|
||||||
"MYID -- Return the node id.",
|
"FORGET <node-id>",
|
||||||
"NODES -- Return cluster configuration seen by node. Output format:",
|
" Remove a node from the cluster.",
|
||||||
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>",
|
"GETKEYSINSLOT <slot> <count>",
|
||||||
"REPLICATE <node-id> -- Configure current node as replica to <node-id>.",
|
" Return key names stored by current node in a slot.",
|
||||||
"RESET [hard|soft] -- Reset current node (default: soft).",
|
"FLUSHSLOTS",
|
||||||
"SET-config-epoch <epoch> - Set config epoch of current node.",
|
" Delete current node own slots information.",
|
||||||
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
"INFO",
|
||||||
"REPLICAS <node-id> -- Return <node-id> replicas.",
|
" Return information about the cluster.",
|
||||||
"SAVECONFIG - Force saving cluster configuration on disk.",
|
"KEYSLOT <key>",
|
||||||
"SLOTS -- Return information about slots range mappings. Each range is made of:",
|
" Return the hash slot for <key>.",
|
||||||
|
"MEET <ip> <port> [<bus-port>]",
|
||||||
|
" Connect nodes into a working cluster.",
|
||||||
|
"MYID",
|
||||||
|
" Return the node id.",
|
||||||
|
"NODES",
|
||||||
|
" Return cluster configuration seen by node. Output format:",
|
||||||
|
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...",
|
||||||
|
"REPLICATE <node-id>",
|
||||||
|
" Configure current node as replica to <node-id>.",
|
||||||
|
"RESET [HARD|SOFT]",
|
||||||
|
" Reset current node (default: soft).",
|
||||||
|
"SET-CONFIG-EPOCH <epoch>",
|
||||||
|
" Set config epoch of current node.",
|
||||||
|
"SETSLOT <slot> (IMPORTING|MIGRATING|STABLE|NODE <node-id>)",
|
||||||
|
" Set slot state.",
|
||||||
|
"REPLICAS <node-id>",
|
||||||
|
" Return <node-id> replicas.",
|
||||||
|
"SAVECONFIG",
|
||||||
|
" Force saving cluster configuration on disk.",
|
||||||
|
"SLOTS",
|
||||||
|
" Return information about slots range mappings. Each range is made of:",
|
||||||
" start, end, master and replicas IP addresses, ports and ids",
|
" start, end, master and replicas IP addresses, ports and ids",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
@ -4577,6 +4661,9 @@ NULL
|
|||||||
g_pserver->cluster->migrating_slots_to[slot])
|
g_pserver->cluster->migrating_slots_to[slot])
|
||||||
g_pserver->cluster->migrating_slots_to[slot] = NULL;
|
g_pserver->cluster->migrating_slots_to[slot] = NULL;
|
||||||
|
|
||||||
|
clusterDelSlot(slot);
|
||||||
|
clusterAddSlot(n,slot);
|
||||||
|
|
||||||
/* If this node was importing this slot, assigning the slot to
|
/* If this node was importing this slot, assigning the slot to
|
||||||
* itself also clears the importing status. */
|
* itself also clears the importing status. */
|
||||||
if (n == myself &&
|
if (n == myself &&
|
||||||
@ -4596,9 +4683,10 @@ NULL
|
|||||||
"configEpoch updated after importing slot %d", slot);
|
"configEpoch updated after importing slot %d", slot);
|
||||||
}
|
}
|
||||||
g_pserver->cluster->importing_slots_from[slot] = NULL;
|
g_pserver->cluster->importing_slots_from[slot] = NULL;
|
||||||
|
/* After importing this slot, let the other nodes know as
|
||||||
|
* soon as possible. */
|
||||||
|
clusterBroadcastPong(CLUSTER_BROADCAST_ALL);
|
||||||
}
|
}
|
||||||
clusterDelSlot(slot);
|
|
||||||
clusterAddSlot(n,slot);
|
|
||||||
} else {
|
} else {
|
||||||
addReplyError(c,
|
addReplyError(c,
|
||||||
"Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP");
|
"Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP");
|
||||||
@ -4844,7 +4932,7 @@ NULL
|
|||||||
takeover = 1;
|
takeover = 1;
|
||||||
force = 1; /* Takeover also implies force. */
|
force = 1; /* Takeover also implies force. */
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4935,7 +5023,7 @@ NULL
|
|||||||
} else if (!strcasecmp(szFromObj(c->argv[2]),"soft")) {
|
} else if (!strcasecmp(szFromObj(c->argv[2]),"soft")) {
|
||||||
hard = 0;
|
hard = 0;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5007,6 +5095,9 @@ int verifyDumpPayload(unsigned char *p, size_t len) {
|
|||||||
rdbver = (footer[1] << 8) | footer[0];
|
rdbver = (footer[1] << 8) | footer[0];
|
||||||
if (rdbver > RDB_VERSION) return C_ERR;
|
if (rdbver > RDB_VERSION) return C_ERR;
|
||||||
|
|
||||||
|
if (cserver.skip_checksum_validation)
|
||||||
|
return C_OK;
|
||||||
|
|
||||||
/* Verify CRC64 */
|
/* Verify CRC64 */
|
||||||
crc = crc64(0,p,len-8);
|
crc = crc64(0,p,len-8);
|
||||||
memrev64ifbe(&crc);
|
memrev64ifbe(&crc);
|
||||||
@ -5066,7 +5157,7 @@ void mvccrestoreCommand(client *c) {
|
|||||||
setMvccTstamp(obj, mvcc);
|
setMvccTstamp(obj, mvcc);
|
||||||
|
|
||||||
/* Create the key and set the TTL if any */
|
/* Create the key and set the TTL if any */
|
||||||
if (dbMerge(c->db,key,obj,true)) {
|
if (dbMerge(c->db,szFromObj(key),obj,true)) {
|
||||||
if (expire >= 0) {
|
if (expire >= 0) {
|
||||||
setExpire(c,c->db,key,nullptr,expire);
|
setExpire(c,c->db,key,nullptr,expire);
|
||||||
}
|
}
|
||||||
@ -5115,7 +5206,7 @@ void restoreCommand(client *c) {
|
|||||||
}
|
}
|
||||||
j++; /* Consume additional arg. */
|
j++; /* Consume additional arg. */
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5123,7 +5214,7 @@ void restoreCommand(client *c) {
|
|||||||
/* Make sure this key does not already exist here... */
|
/* Make sure this key does not already exist here... */
|
||||||
robj *key = c->argv[1];
|
robj *key = c->argv[1];
|
||||||
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
|
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
|
||||||
addReply(c,shared.busykeyerr);
|
addReplyErrorObject(c,shared.busykeyerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5236,8 +5327,7 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
|
|||||||
conn = g_pserver->tls_cluster ? connCreateTLS() : connCreateSocket();
|
conn = g_pserver->tls_cluster ? connCreateTLS() : connCreateSocket();
|
||||||
if (connBlockingConnect(conn, szFromObj(c->argv[1]), atoi(szFromObj(c->argv[2])), timeout)
|
if (connBlockingConnect(conn, szFromObj(c->argv[1]), atoi(szFromObj(c->argv[2])), timeout)
|
||||||
!= C_OK) {
|
!= C_OK) {
|
||||||
addReplySds(c,
|
addReplyError(c,"-IOERR error or timeout connecting to the client");
|
||||||
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
|
||||||
connClose(conn);
|
connClose(conn);
|
||||||
sdsfree(name);
|
sdsfree(name);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -5324,7 +5414,7 @@ void migrateCommand(client *c) {
|
|||||||
replace = 1;
|
replace = 1;
|
||||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"auth")) {
|
} else if (!strcasecmp(szFromObj(c->argv[j]),"auth")) {
|
||||||
if (!moreargs) {
|
if (!moreargs) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
j++;
|
j++;
|
||||||
@ -5347,7 +5437,7 @@ void migrateCommand(client *c) {
|
|||||||
num_keys = c->argc - j - 1;
|
num_keys = c->argc - j - 1;
|
||||||
break; /* All the remaining args are keys. */
|
break; /* All the remaining args are keys. */
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5840,7 +5930,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
|||||||
* cluster is down. */
|
* cluster is down. */
|
||||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (!(cmd->flags & CMD_READONLY) && !(cmd->proc == evalCommand)
|
} else if ((cmd->flags & CMD_WRITE) && !(cmd->proc == evalCommand)
|
||||||
&& !(cmd->proc == evalShaCommand))
|
&& !(cmd->proc == evalShaCommand))
|
||||||
{
|
{
|
||||||
/* The cluster is configured to allow read only commands
|
/* The cluster is configured to allow read only commands
|
||||||
@ -5889,11 +5979,10 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
|||||||
/* Handle the read-only client case reading from a slave: if this
|
/* 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
|
* node is a slave and the request is about a hash slot our master
|
||||||
* is serving, we can reply without redirection. */
|
* is serving, we can reply without redirection. */
|
||||||
int is_readonly_command = (c->cmd->flags & CMD_READONLY) ||
|
int is_write_command = (c->cmd->flags & CMD_WRITE) ||
|
||||||
(c->cmd->proc == execCommand && !(c->mstate.cmd_inv_flags & CMD_READONLY));
|
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
|
||||||
if (c->flags & CLIENT_READONLY &&
|
if (c->flags & CLIENT_READONLY &&
|
||||||
(is_readonly_command || cmd->proc == evalCommand ||
|
(!is_write_command || cmd->proc == evalCommand || cmd->proc == evalShaCommand) &&
|
||||||
cmd->proc == evalShaCommand) &&
|
|
||||||
nodeIsSlave(myself) &&
|
nodeIsSlave(myself) &&
|
||||||
myself->slaveof == n)
|
myself->slaveof == n)
|
||||||
{
|
{
|
||||||
@ -5915,23 +6004,23 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
|||||||
* be set to the hash slot that caused the redirection. */
|
* be set to the hash slot that caused the redirection. */
|
||||||
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) {
|
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) {
|
||||||
if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
|
if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
|
||||||
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
|
addReplyError(c,"-CROSSSLOT Keys in request don't hash to the same slot");
|
||||||
} else if (error_code == CLUSTER_REDIR_UNSTABLE) {
|
} else if (error_code == CLUSTER_REDIR_UNSTABLE) {
|
||||||
/* The request spawns multiple keys in the same slot,
|
/* The request spawns multiple keys in the same slot,
|
||||||
* but the slot is not "stable" currently as there is
|
* but the slot is not "stable" currently as there is
|
||||||
* a migration or import in progress. */
|
* a migration or import in progress. */
|
||||||
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
|
addReplyError(c,"-TRYAGAIN Multiple keys request during rehashing of slot");
|
||||||
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
|
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
|
||||||
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down\r\n"));
|
addReplyError(c,"-CLUSTERDOWN The cluster is down");
|
||||||
} else if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
|
} else if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
|
||||||
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down and only accepts read commands\r\n"));
|
addReplyError(c,"-CLUSTERDOWN The cluster is down and only accepts read commands");
|
||||||
} else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) {
|
} else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) {
|
||||||
addReplySds(c,sdsnew("-CLUSTERDOWN Hash slot not served\r\n"));
|
addReplyError(c,"-CLUSTERDOWN Hash slot not served");
|
||||||
} else if (error_code == CLUSTER_REDIR_MOVED ||
|
} else if (error_code == CLUSTER_REDIR_MOVED ||
|
||||||
error_code == CLUSTER_REDIR_ASK)
|
error_code == CLUSTER_REDIR_ASK)
|
||||||
{
|
{
|
||||||
addReplySds(c,sdscatprintf(sdsempty(),
|
addReplyErrorSds(c,sdscatprintf(sdsempty(),
|
||||||
"-%s %d %s:%d\r\n",
|
"-%s %d %s:%d",
|
||||||
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
||||||
hashslot,n->ip,n->port));
|
hashslot,n->ip,n->port));
|
||||||
} else {
|
} else {
|
||||||
@ -5985,6 +6074,15 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
|||||||
node = myself;
|
node = myself;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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) &&
|
||||||
|
nodeIsSlave(myself) && myself->slaveof == node)
|
||||||
|
{
|
||||||
|
node = myself;
|
||||||
|
}
|
||||||
|
|
||||||
/* We send an error and unblock the client if:
|
/* We send an error and unblock the client if:
|
||||||
* 1) The slot is unassigned, emitting a cluster down error.
|
* 1) The slot is unassigned, emitting a cluster down error.
|
||||||
* 2) The slot is not handled by this node, nor being imported. */
|
* 2) The slot is not handled by this node, nor being imported. */
|
||||||
|
@ -83,6 +83,7 @@ typedef struct clusterLink {
|
|||||||
#define CLUSTER_TODO_UPDATE_STATE (1<<1)
|
#define CLUSTER_TODO_UPDATE_STATE (1<<1)
|
||||||
#define CLUSTER_TODO_SAVE_CONFIG (1<<2)
|
#define CLUSTER_TODO_SAVE_CONFIG (1<<2)
|
||||||
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
|
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
|
||||||
|
#define CLUSTER_TODO_HANDLE_MANUALFAILOVER (1<<4)
|
||||||
|
|
||||||
/* Message types.
|
/* Message types.
|
||||||
*
|
*
|
||||||
@ -121,6 +122,7 @@ typedef struct clusterNode {
|
|||||||
int flags; /* CLUSTER_NODE_... */
|
int flags; /* CLUSTER_NODE_... */
|
||||||
uint64_t configEpoch; /* Last configEpoch observed for this node */
|
uint64_t configEpoch; /* Last configEpoch observed for this node */
|
||||||
unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
|
unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
|
||||||
|
sds slots_info; /* Slots info represented by string. */
|
||||||
int numslots; /* Number of slots handled by this node */
|
int numslots; /* Number of slots handled by this node */
|
||||||
int numslaves; /* Number of slave nodes, if this is a master */
|
int numslaves; /* Number of slave nodes, if this is a master */
|
||||||
struct clusterNode **slaves; /* pointers to slave nodes */
|
struct clusterNode **slaves; /* pointers to slave nodes */
|
||||||
|
@ -73,7 +73,7 @@ public:
|
|||||||
m_max = m_max + 4;
|
m_max = m_max + 4;
|
||||||
|
|
||||||
m_data = (T*)zrealloc(m_data, sizeof(T) * m_max, MALLOC_LOCAL);
|
m_data = (T*)zrealloc(m_data, sizeof(T) * m_max, MALLOC_LOCAL);
|
||||||
m_max = zmalloc_usable(m_data) / sizeof(T);
|
m_max = zmalloc_usable_size(m_data) / sizeof(T);
|
||||||
}
|
}
|
||||||
assert(idx < m_max);
|
assert(idx < m_max);
|
||||||
where = m_data + idx;
|
where = m_data + idx;
|
||||||
|
441
src/config.cpp
441
src/config.cpp
@ -115,6 +115,19 @@ configEnum oom_score_adj_enum[] = {
|
|||||||
{NULL, 0}
|
{NULL, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
configEnum acl_pubsub_default_enum[] = {
|
||||||
|
{"allchannels", USER_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}
|
||||||
|
};
|
||||||
|
|
||||||
/* Output buffer limits presets. */
|
/* Output buffer limits presets. */
|
||||||
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
|
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
|
||||||
{0, 0, 0}, /* normal */
|
{0, 0, 0}, /* normal */
|
||||||
@ -155,6 +168,15 @@ typedef struct stringConfigData {
|
|||||||
be stored as a NULL value. */
|
be stored as a NULL value. */
|
||||||
} stringConfigData;
|
} stringConfigData;
|
||||||
|
|
||||||
|
typedef struct sdsConfigData {
|
||||||
|
sds *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)(sds val, const char **err); /* Optional function to check validity of new value (generic doc above) */
|
||||||
|
int (*update_fn)(sds val, sds prev, const char **err); /* Optional function to apply new value at runtime (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 {
|
typedef struct enumConfigData {
|
||||||
int *config; /* The pointer to the server config this value is stored in */
|
int *config; /* The pointer to the server config this value is stored in */
|
||||||
configEnum *enum_value; /* The underlying enum type this data represents */
|
configEnum *enum_value; /* The underlying enum type this data represents */
|
||||||
@ -201,6 +223,7 @@ typedef struct numericConfigData {
|
|||||||
typedef union typeData {
|
typedef union typeData {
|
||||||
boolConfigData yesno;
|
boolConfigData yesno;
|
||||||
stringConfigData string;
|
stringConfigData string;
|
||||||
|
sdsConfigData sds;
|
||||||
enumConfigData enumd;
|
enumConfigData enumd;
|
||||||
numericConfigData numeric;
|
numericConfigData numeric;
|
||||||
} typeData;
|
} typeData;
|
||||||
@ -375,6 +398,7 @@ void loadServerConfigFromString(char *config) {
|
|||||||
int linenum = 0, totlines, i;
|
int linenum = 0, totlines, i;
|
||||||
int slaveof_linenum = 0;
|
int slaveof_linenum = 0;
|
||||||
sds *lines;
|
sds *lines;
|
||||||
|
int save_loaded = 0;
|
||||||
|
|
||||||
lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
|
lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
|
||||||
|
|
||||||
@ -447,6 +471,14 @@ void loadServerConfigFromString(char *config) {
|
|||||||
err = "Invalid socket file permissions"; goto loaderr;
|
err = "Invalid socket file permissions"; goto loaderr;
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(argv[0],"save")) {
|
} else if (!strcasecmp(argv[0],"save")) {
|
||||||
|
/* We don't reset save params before loading, because if they're not part
|
||||||
|
* of the file the defaults should be used.
|
||||||
|
*/
|
||||||
|
if (!save_loaded) {
|
||||||
|
save_loaded = 1;
|
||||||
|
resetServerSaveParams();
|
||||||
|
}
|
||||||
|
|
||||||
if (argc == 3) {
|
if (argc == 3) {
|
||||||
int seconds = atoi(argv[1]);
|
int seconds = atoi(argv[1]);
|
||||||
int changes = atoi(argv[2]);
|
int changes = atoi(argv[2]);
|
||||||
@ -480,9 +512,7 @@ void loadServerConfigFromString(char *config) {
|
|||||||
fclose(logfp);
|
fclose(logfp);
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(argv[0],"include") && argc == 2) {
|
} else if (!strcasecmp(argv[0],"include") && argc == 2) {
|
||||||
loadServerConfig(argv[1],NULL);
|
loadServerConfig(argv[1], 0, NULL);
|
||||||
} else if ((!strcasecmp(argv[0],"client-query-buffer-limit")) && argc == 2) {
|
|
||||||
cserver.client_max_querybuf_len = memtoll(argv[1],NULL);
|
|
||||||
} else if ((!strcasecmp(argv[0],"slaveof") ||
|
} else if ((!strcasecmp(argv[0],"slaveof") ||
|
||||||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
|
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
|
||||||
slaveof_linenum = linenum;
|
slaveof_linenum = linenum;
|
||||||
@ -494,7 +524,7 @@ void loadServerConfigFromString(char *config) {
|
|||||||
while ((ln = listNext(&li)))
|
while ((ln = listNext(&li)))
|
||||||
{
|
{
|
||||||
struct redisMaster *mi = (struct redisMaster*)listNodeValue(ln);
|
struct redisMaster *mi = (struct redisMaster*)listNodeValue(ln);
|
||||||
zfree(mi->masterauth);
|
sdsfree(mi->masterauth);
|
||||||
zfree(mi->masteruser);
|
zfree(mi->masteruser);
|
||||||
zfree(mi->repl_transfer_tmpfile);
|
zfree(mi->repl_transfer_tmpfile);
|
||||||
delete mi->staleKeyMap;
|
delete mi->staleKeyMap;
|
||||||
@ -515,21 +545,6 @@ void loadServerConfigFromString(char *config) {
|
|||||||
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
||||||
goto loaderr;
|
goto loaderr;
|
||||||
}
|
}
|
||||||
/* 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. */
|
|
||||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
|
||||||
sdsfree(g_pserver->requirepass);
|
|
||||||
g_pserver->requirepass = NULL;
|
|
||||||
if (sdslen(argv[1])) {
|
|
||||||
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
|
|
||||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
|
||||||
sdsfree(aclop);
|
|
||||||
g_pserver->requirepass = sdsnew(argv[1]);
|
|
||||||
} else {
|
|
||||||
ACLSetUser(DefaultUser,"nopass",-1);
|
|
||||||
}
|
|
||||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
|
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
|
||||||
/* DEAD OPTION */
|
/* DEAD OPTION */
|
||||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
|
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
|
||||||
@ -613,8 +628,7 @@ void loadServerConfigFromString(char *config) {
|
|||||||
err = "sentinel directive while not in sentinel mode";
|
err = "sentinel directive while not in sentinel mode";
|
||||||
goto loaderr;
|
goto loaderr;
|
||||||
}
|
}
|
||||||
err = sentinelHandleConfiguration(argv+1,argc-1);
|
queueSentinelConfig(argv+1,argc-1,linenum,lines[i]);
|
||||||
if (err) goto loaderr;
|
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(argv[0],"scratch-file-path")) {
|
} else if (!strcasecmp(argv[0],"scratch-file-path")) {
|
||||||
#ifdef USE_MEMKIND
|
#ifdef USE_MEMKIND
|
||||||
@ -697,28 +711,31 @@ loaderr:
|
|||||||
* Both filename and options can be NULL, in such a case are considered
|
* 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
|
* empty. This way loadServerConfig can be used to just load a file or
|
||||||
* just load a string. */
|
* just load a string. */
|
||||||
void loadServerConfig(char *filename, char *options) {
|
void loadServerConfig(char *filename, char config_from_stdin, char *options) {
|
||||||
sds config = sdsempty();
|
sds config = sdsempty();
|
||||||
char buf[CONFIG_MAX_LINE+1];
|
char buf[CONFIG_MAX_LINE+1];
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
/* Load the file content */
|
/* Load the file content */
|
||||||
if (filename) {
|
if (filename) {
|
||||||
FILE *fp;
|
|
||||||
|
|
||||||
if (filename[0] == '-' && filename[1] == '\0') {
|
|
||||||
fp = stdin;
|
|
||||||
} else {
|
|
||||||
if ((fp = fopen(filename,"r")) == NULL) {
|
if ((fp = fopen(filename,"r")) == NULL) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Fatal error, can't open config file '%s': %s",
|
"Fatal error, can't open config file '%s': %s",
|
||||||
filename, strerror(errno));
|
filename, strerror(errno));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
|
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
|
||||||
config = sdscat(config,buf);
|
config = sdscat(config,buf);
|
||||||
if (fp != stdin) fclose(fp);
|
fclose(fp);
|
||||||
}
|
}
|
||||||
|
/* Append content from stdin */
|
||||||
|
if (config_from_stdin) {
|
||||||
|
serverLog(LL_WARNING,"Reading config from stdin");
|
||||||
|
fp = stdin;
|
||||||
|
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
|
||||||
|
config = sdscat(config,buf);
|
||||||
|
}
|
||||||
|
|
||||||
/* Append the additional options */
|
/* Append the additional options */
|
||||||
if (options) {
|
if (options) {
|
||||||
config = sdscat(config,"\n");
|
config = sdscat(config,"\n");
|
||||||
@ -785,23 +802,38 @@ void configSetCommand(client *c) {
|
|||||||
if (0) { /* this starts the config_set macros else-if chain. */
|
if (0) { /* this starts the config_set macros else-if chain. */
|
||||||
|
|
||||||
/* Special fields that can't be handled with general macros. */
|
/* Special fields that can't be handled with general macros. */
|
||||||
config_set_special_field("requirepass") {
|
config_set_special_field("bind") {
|
||||||
if (sdslen(szFromObj(o)) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
|
int vlen;
|
||||||
/* The old "requirepass" directive just translates to setting
|
sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen);
|
||||||
* a password to the default user. The only thing we do
|
|
||||||
* additionally is to remember the cleartext password in this
|
if (vlen < 1 || vlen > CONFIG_BINDADDR_MAX) {
|
||||||
* case, for backward compatibility with Redis <= 5. */
|
addReplyError(c, "Too many bind addresses specified.");
|
||||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
sdsfreesplitres(v, vlen);
|
||||||
sdsfree(g_pserver->requirepass);
|
return;
|
||||||
g_pserver->requirepass = NULL;
|
|
||||||
if (sdslen(szFromObj(o))) {
|
|
||||||
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)ptrFromObj(o));
|
|
||||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
|
||||||
sdsfree(aclop);
|
|
||||||
g_pserver->requirepass = sdsnew(szFromObj(o));
|
|
||||||
} else {
|
|
||||||
ACLSetUser(DefaultUser,"nopass",-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changeBindAddr(v, vlen, true) == C_ERR) {
|
||||||
|
addReplyError(c, "Failed to bind to specified addresses.");
|
||||||
|
sdsfreesplitres(v, vlen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Now run the config change on the other threads
|
||||||
|
for (int ithread = 0; ithread < cserver.cthreads; ++ithread) {
|
||||||
|
if (&g_pserver->rgthreadvar[ithread] != serverTL) {
|
||||||
|
incrRefCount(o);
|
||||||
|
aePostFunction(g_pserver->rgthreadvar[ithread].el, [o]{
|
||||||
|
int vlen;
|
||||||
|
sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen);
|
||||||
|
if (changeBindAddr(v, vlen, false) == C_ERR) {
|
||||||
|
serverLog(LL_WARNING, "Failed to change the bind address for a thread. Server will still be listening on old addresses.");
|
||||||
|
}
|
||||||
|
sdsfreesplitres(v, vlen);
|
||||||
|
decrRefCount(o);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sdsfreesplitres(v, vlen);
|
||||||
} config_set_special_field("save") {
|
} config_set_special_field("save") {
|
||||||
int vlen, j;
|
int vlen, j;
|
||||||
sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen);
|
sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen);
|
||||||
@ -910,10 +942,6 @@ void configSetCommand(client *c) {
|
|||||||
enableWatchdog(ll);
|
enableWatchdog(ll);
|
||||||
else
|
else
|
||||||
disableWatchdog();
|
disableWatchdog();
|
||||||
/* Memory fields.
|
|
||||||
* config_set_memory_field(name,var) */
|
|
||||||
} config_set_memory_field(
|
|
||||||
"client-query-buffer-limit",cserver.client_max_querybuf_len) {
|
|
||||||
/* Everything else is an error... */
|
/* Everything else is an error... */
|
||||||
} config_set_else {
|
} config_set_else {
|
||||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||||
@ -948,7 +976,7 @@ badfmt: /* Bad format errors */
|
|||||||
addReplyBulkCString(c,_var ? _var : ""); \
|
addReplyBulkCString(c,_var ? _var : ""); \
|
||||||
matches++; \
|
matches++; \
|
||||||
} \
|
} \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
#define config_get_bool_field(_name,_var) do { \
|
#define config_get_bool_field(_name,_var) do { \
|
||||||
if (stringmatch(pattern,_name,1)) { \
|
if (stringmatch(pattern,_name,1)) { \
|
||||||
@ -956,7 +984,7 @@ badfmt: /* Bad format errors */
|
|||||||
addReplyBulkCString(c,_var ? "yes" : "no"); \
|
addReplyBulkCString(c,_var ? "yes" : "no"); \
|
||||||
matches++; \
|
matches++; \
|
||||||
} \
|
} \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
#define config_get_numerical_field(_name,_var) do { \
|
#define config_get_numerical_field(_name,_var) do { \
|
||||||
if (stringmatch(pattern,_name,1)) { \
|
if (stringmatch(pattern,_name,1)) { \
|
||||||
@ -965,8 +993,7 @@ badfmt: /* Bad format errors */
|
|||||||
addReplyBulkCString(c,buf); \
|
addReplyBulkCString(c,buf); \
|
||||||
matches++; \
|
matches++; \
|
||||||
} \
|
} \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
|
|
||||||
void configGetCommand(client *c) {
|
void configGetCommand(client *c) {
|
||||||
robj *o = c->argv[2];
|
robj *o = c->argv[2];
|
||||||
@ -994,7 +1021,6 @@ void configGetCommand(client *c) {
|
|||||||
config_get_string_field("logfile",g_pserver->logfile);
|
config_get_string_field("logfile",g_pserver->logfile);
|
||||||
|
|
||||||
/* Numerical values */
|
/* Numerical values */
|
||||||
config_get_numerical_field("client-query-buffer-limit",cserver.client_max_querybuf_len);
|
|
||||||
config_get_numerical_field("watchdog-period",g_pserver->watchdog_period);
|
config_get_numerical_field("watchdog-period",g_pserver->watchdog_period);
|
||||||
|
|
||||||
/* Everything we can't handle with macros follows. */
|
/* Everything we can't handle with macros follows. */
|
||||||
@ -1045,7 +1071,7 @@ void configGetCommand(client *c) {
|
|||||||
}
|
}
|
||||||
if (stringmatch(pattern,"unixsocketperm",1)) {
|
if (stringmatch(pattern,"unixsocketperm",1)) {
|
||||||
char buf[32];
|
char buf[32];
|
||||||
snprintf(buf,sizeof(buf),"%o",g_pserver->unixsocketperm);
|
snprintf(buf,sizeof(buf),"%lo",(unsigned long)g_pserver->unixsocketperm);
|
||||||
addReplyBulkCString(c,"unixsocketperm");
|
addReplyBulkCString(c,"unixsocketperm");
|
||||||
addReplyBulkCString(c,buf);
|
addReplyBulkCString(c,buf);
|
||||||
matches++;
|
matches++;
|
||||||
@ -1099,16 +1125,6 @@ void configGetCommand(client *c) {
|
|||||||
sdsfree(aux);
|
sdsfree(aux);
|
||||||
matches++;
|
matches++;
|
||||||
}
|
}
|
||||||
if (stringmatch(pattern,"requirepass",1)) {
|
|
||||||
addReplyBulkCString(c,"requirepass");
|
|
||||||
sds password = g_pserver->requirepass;
|
|
||||||
if (password) {
|
|
||||||
addReplyBulkCBuffer(c,password,sdslen(password));
|
|
||||||
} else {
|
|
||||||
addReplyBulkCString(c,"");
|
|
||||||
}
|
|
||||||
matches++;
|
|
||||||
}
|
|
||||||
if (stringmatch(pattern,"oom-score-adj-values",0)) {
|
if (stringmatch(pattern,"oom-score-adj-values",0)) {
|
||||||
sds buf = sdsempty();
|
sds buf = sdsempty();
|
||||||
int j;
|
int j;
|
||||||
@ -1157,7 +1173,8 @@ dictType optionToLineDictType = {
|
|||||||
NULL, /* val dup */
|
NULL, /* val dup */
|
||||||
dictSdsKeyCaseCompare, /* key compare */
|
dictSdsKeyCaseCompare, /* key compare */
|
||||||
dictSdsDestructor, /* key destructor */
|
dictSdsDestructor, /* key destructor */
|
||||||
dictListDestructor /* val destructor */
|
dictListDestructor, /* val destructor */
|
||||||
|
NULL /* allow to expand */
|
||||||
};
|
};
|
||||||
|
|
||||||
dictType optionSetDictType = {
|
dictType optionSetDictType = {
|
||||||
@ -1166,7 +1183,8 @@ dictType optionSetDictType = {
|
|||||||
NULL, /* val dup */
|
NULL, /* val dup */
|
||||||
dictSdsKeyCaseCompare, /* key compare */
|
dictSdsKeyCaseCompare, /* key compare */
|
||||||
dictSdsDestructor, /* key destructor */
|
dictSdsDestructor, /* key destructor */
|
||||||
NULL /* val destructor */
|
NULL, /* val destructor */
|
||||||
|
NULL /* allow to expand */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* The config rewrite state. */
|
/* The config rewrite state. */
|
||||||
@ -1270,13 +1288,22 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
|||||||
char *p = strstr(argv[0],"slave");
|
char *p = strstr(argv[0],"slave");
|
||||||
if (p) {
|
if (p) {
|
||||||
sds alt = sdsempty();
|
sds alt = sdsempty();
|
||||||
alt = sdscatlen(alt,argv[0],p-argv[0]);;
|
alt = sdscatlen(alt,argv[0],p-argv[0]);
|
||||||
alt = sdscatlen(alt,"replica",7);
|
alt = sdscatlen(alt,"replica",7);
|
||||||
alt = sdscatlen(alt,p+5,strlen(p+5));
|
alt = sdscatlen(alt,p+5,strlen(p+5));
|
||||||
sdsfree(argv[0]);
|
sdsfree(argv[0]);
|
||||||
argv[0] = alt;
|
argv[0] = alt;
|
||||||
}
|
}
|
||||||
|
/* If this is sentinel config, we use sentinel "sentinel <config>" as option
|
||||||
|
to avoid messing up the sequence. */
|
||||||
|
if (g_pserver->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);
|
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
|
||||||
|
}
|
||||||
sdsfreesplitres(argv,argc);
|
sdsfreesplitres(argv,argc);
|
||||||
}
|
}
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
@ -1394,6 +1421,28 @@ void rewriteConfigStringOption(struct rewriteConfigState *state, const char *opt
|
|||||||
rewriteConfigRewriteLine(state,option,line,force);
|
rewriteConfigRewriteLine(state,option,line,force);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Rewrite a SDS string option. */
|
||||||
|
void rewriteConfigSdsOption(struct rewriteConfigState *state, const char *option, sds value, const sds 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 && sdscmp(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. */
|
/* Rewrite a numerical (long long range) option. */
|
||||||
void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) {
|
void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) {
|
||||||
int force = value != defvalue;
|
int force = value != defvalue;
|
||||||
@ -1597,26 +1646,6 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
|||||||
rewriteConfigRewriteLine(state,option,line,force);
|
rewriteConfigRewriteLine(state,option,line,force);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rewrite the requirepass option. */
|
|
||||||
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, const char *option) {
|
|
||||||
int force = 1;
|
|
||||||
sds line;
|
|
||||||
sds password = g_pserver->requirepass;
|
|
||||||
|
|
||||||
/* If there is no password set, we don't want the requirepass option
|
|
||||||
* to be present in the configuration at all. */
|
|
||||||
if (password == NULL) {
|
|
||||||
rewriteConfigMarkAsProcessed(state,option);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
line = sdsnew(option);
|
|
||||||
line = sdscatlen(line, " ", 1);
|
|
||||||
line = sdscatsds(line, password);
|
|
||||||
|
|
||||||
rewriteConfigRewriteLine(state,option,line,force);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Glue together the configuration lines in the current configuration
|
/* Glue together the configuration lines in the current configuration
|
||||||
* rewrite state into a single string, stripping multiple empty lines. */
|
* rewrite state into a single string, stripping multiple empty lines. */
|
||||||
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
|
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
|
||||||
@ -1724,7 +1753,7 @@ int rewriteConfigOverwriteFile(char *configfile, sds content) {
|
|||||||
|
|
||||||
if (fsync(fd))
|
if (fsync(fd))
|
||||||
serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno));
|
serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno));
|
||||||
else if (fchmod(fd, 0644) == -1)
|
else if (fchmod(fd, 0644 & ~g_pserver->umask) == -1)
|
||||||
serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno));
|
serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno));
|
||||||
else if (rename(tmp_conffile, configfile) == -1)
|
else if (rename(tmp_conffile, configfile) == -1)
|
||||||
serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno));
|
serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno));
|
||||||
@ -1773,8 +1802,6 @@ int rewriteConfig(char *path, int force_all) {
|
|||||||
rewriteConfigUserOption(state);
|
rewriteConfigUserOption(state);
|
||||||
rewriteConfigDirOption(state);
|
rewriteConfigDirOption(state);
|
||||||
rewriteConfigSlaveofOption(state,"replicaof");
|
rewriteConfigSlaveofOption(state,"replicaof");
|
||||||
rewriteConfigRequirepassOption(state,"requirepass");
|
|
||||||
rewriteConfigBytesOption(state,"client-query-buffer-limit",cserver.client_max_querybuf_len,PROTO_MAX_QUERYBUF_LEN);
|
|
||||||
rewriteConfigStringOption(state,"cluster-config-file",g_pserver->cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
rewriteConfigStringOption(state,"cluster-config-file",g_pserver->cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||||
rewriteConfigNotifykeyspaceeventsOption(state);
|
rewriteConfigNotifykeyspaceeventsOption(state);
|
||||||
rewriteConfigClientoutputbufferlimitOption(state);
|
rewriteConfigClientoutputbufferlimitOption(state);
|
||||||
@ -1875,22 +1902,14 @@ constexpr standardConfig createBoolConfig(const char *name, const char *alias, i
|
|||||||
|
|
||||||
/* String Configs */
|
/* String Configs */
|
||||||
static void stringConfigInit(typeData data) {
|
static void stringConfigInit(typeData data) {
|
||||||
if (data.string.convert_empty_to_null) {
|
*data.string.config = (data.string.convert_empty_to_null && !data.string.default_value) ? NULL : zstrdup(data.string.default_value);
|
||||||
*data.string.config = data.string.default_value ? zstrdup(data.string.default_value) : NULL;
|
|
||||||
} else {
|
|
||||||
*data.string.config = zstrdup(data.string.default_value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stringConfigSet(typeData data, sds value, int update, const char **err) {
|
static int stringConfigSet(typeData data, sds value, int update, const char **err) {
|
||||||
if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err))
|
if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err))
|
||||||
return 0;
|
return 0;
|
||||||
char *prev = *data.string.config;
|
char *prev = *data.string.config;
|
||||||
if (data.string.convert_empty_to_null) {
|
*data.string.config = (data.string.convert_empty_to_null && !value[0]) ? NULL : zstrdup(value);
|
||||||
*data.string.config = value[0] ? zstrdup(value) : NULL;
|
|
||||||
} else {
|
|
||||||
*data.string.config = zstrdup(value);
|
|
||||||
}
|
|
||||||
if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) {
|
if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) {
|
||||||
zfree(*data.string.config);
|
zfree(*data.string.config);
|
||||||
*data.string.config = prev;
|
*data.string.config = prev;
|
||||||
@ -1908,6 +1927,38 @@ static void stringConfigRewrite(typeData data, const char *name, struct rewriteC
|
|||||||
rewriteConfigStringOption(state, name,*(data.string.config), data.string.default_value);
|
rewriteConfigStringOption(state, name,*(data.string.config), data.string.default_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* SDS Configs */
|
||||||
|
static void sdsConfigInit(typeData data) {
|
||||||
|
*data.sds.config = (data.sds.convert_empty_to_null && !data.sds.default_value) ? NULL: sdsnew(data.sds.default_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sdsConfigSet(typeData data, sds value, int update, const char **err) {
|
||||||
|
if (data.sds.is_valid_fn && !data.sds.is_valid_fn(value, err))
|
||||||
|
return 0;
|
||||||
|
sds prev = *data.sds.config;
|
||||||
|
*data.sds.config = (data.sds.convert_empty_to_null && (sdslen(value) == 0)) ? NULL : sdsdup(value);
|
||||||
|
if (update && data.sds.update_fn && !data.sds.update_fn(*data.sds.config, prev, err)) {
|
||||||
|
sdsfree(*data.sds.config);
|
||||||
|
*data.sds.config = prev;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
sdsfree(prev);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sdsConfigGet(client *c, typeData data) {
|
||||||
|
if (*data.sds.config) {
|
||||||
|
addReplyBulkSds(c, sdsdup(*data.sds.config));
|
||||||
|
} else {
|
||||||
|
addReplyBulkCString(c, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
|
||||||
|
rewriteConfigSdsOption(state, name, *(data.sds.config), data.sds.default_value ? sdsnew(data.sds.default_value) : NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#define ALLOW_EMPTY_STRING 0
|
#define ALLOW_EMPTY_STRING 0
|
||||||
#define EMPTY_STRING_IS_NULL 1
|
#define EMPTY_STRING_IS_NULL 1
|
||||||
|
|
||||||
@ -1926,6 +1977,21 @@ constexpr standardConfig createStringConfig(const char *name, const char *alias,
|
|||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr standardConfig createSDSConfig(const char *name, const char *alias, int modifiable, int empty_to_null, sds &config_addr, const char *defaultValue, int (*is_valid)(char*,const char**), int (*update)(char*,char*,const char**)) {
|
||||||
|
standardConfig conf = {
|
||||||
|
embedCommonConfig(name, alias, modifiable)
|
||||||
|
embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite)
|
||||||
|
};
|
||||||
|
conf.data.sds = {
|
||||||
|
&(config_addr),
|
||||||
|
(defaultValue),
|
||||||
|
(is_valid),
|
||||||
|
(update),
|
||||||
|
(empty_to_null),
|
||||||
|
};
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
/* Enum configs */
|
/* Enum configs */
|
||||||
static void enumConfigInit(typeData data) {
|
static void enumConfigInit(typeData data) {
|
||||||
*data.enumd.config = data.enumd.default_value;
|
*data.enumd.config = data.enumd.default_value;
|
||||||
@ -1944,7 +2010,7 @@ static int enumConfigSet(typeData data, sds value, int update, const char **err)
|
|||||||
}
|
}
|
||||||
sdsrange(enumerr,0,-3); /* Remove final ", ". */
|
sdsrange(enumerr,0,-3); /* Remove final ", ". */
|
||||||
|
|
||||||
strncpy(loadbuf, enumerr, LOADBUF_SIZE-1);
|
strncpy(loadbuf, enumerr, LOADBUF_SIZE);
|
||||||
loadbuf[LOADBUF_SIZE - 1] = '\0';
|
loadbuf[LOADBUF_SIZE - 1] = '\0';
|
||||||
|
|
||||||
sdsfree(enumerr);
|
sdsfree(enumerr);
|
||||||
@ -2246,6 +2312,25 @@ static int isValidAOFfilename(char *val, const char **err) {
|
|||||||
return 1;
|
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 updateProcTitleTemplate(char *val, char *prev, const char **err) {
|
||||||
|
UNUSED(val);
|
||||||
|
UNUSED(prev);
|
||||||
|
if (redisSetProcTitle(NULL) == C_ERR) {
|
||||||
|
*err = "failed to set process title";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int updateHZ(long long val, long long prev, const char **err) {
|
static int updateHZ(long long val, long long prev, const char **err) {
|
||||||
UNUSED(prev);
|
UNUSED(prev);
|
||||||
UNUSED(err);
|
UNUSED(err);
|
||||||
@ -2258,6 +2343,32 @@ static int updateHZ(long long val, long long prev, const char **err) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int updatePort(long long val, long long prev, const char **err) {
|
||||||
|
/* Do nothing if port is unchanged */
|
||||||
|
if (val == prev) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run this thread to make sure its valid
|
||||||
|
if (changeListenPort(val, &serverTL->ipfd, acceptTcpHandler, true) == C_ERR) {
|
||||||
|
*err = "Unable to listen on this port. Check server logs.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now run the config change on the other threads
|
||||||
|
for (int ithread = 0; ithread < cserver.cthreads; ++ithread) {
|
||||||
|
if (&g_pserver->rgthreadvar[ithread] != serverTL) {
|
||||||
|
aePostFunction(g_pserver->rgthreadvar[ithread].el, [val]{
|
||||||
|
if (changeListenPort(val, &serverTL->ipfd, acceptTcpHandler, false) == C_ERR) {
|
||||||
|
serverLog(LL_WARNING, "Failed to change the listen port for a thread. Server will still be listening on old ports.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int updateJemallocBgThread(int val, int prev, const char **err) {
|
static int updateJemallocBgThread(int val, int prev, const char **err) {
|
||||||
UNUSED(prev);
|
UNUSED(prev);
|
||||||
UNUSED(err);
|
UNUSED(err);
|
||||||
@ -2282,7 +2393,7 @@ static int updateMaxmemory(long long val, long long prev, const char **err) {
|
|||||||
if ((unsigned long long)val < used) {
|
if ((unsigned long long)val < 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.", g_pserver->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.", g_pserver->maxmemory, used);
|
||||||
}
|
}
|
||||||
freeMemoryIfNeededAndSafe();
|
performEvictions();
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -2295,7 +2406,7 @@ static int updateGoodSlaves(long long val, long long prev, const char **err) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int updateMasterAuthConfig(char *, char *, const char **) {
|
static int updateMasterAuthConfig(sds, sds, const char **) {
|
||||||
updateMasterAuth();
|
updateMasterAuth();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -2313,6 +2424,16 @@ static int updateAppendonly(int val, int prev, const char **err) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int updateSighandlerEnabled(int val, int prev, const char **err) {
|
||||||
|
UNUSED(err);
|
||||||
|
UNUSED(prev);
|
||||||
|
if (val)
|
||||||
|
setupSignalHandlers();
|
||||||
|
else
|
||||||
|
removeSignalHandlers();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int updateMaxclients(long long val, long long prev, const char **err) {
|
static int updateMaxclients(long long val, long long prev, const char **err) {
|
||||||
/* Try to check if the OS is capable of supporting so many FDs. */
|
/* Try to check if the OS is capable of supporting so many FDs. */
|
||||||
if (val > prev) {
|
if (val > prev) {
|
||||||
@ -2389,6 +2510,17 @@ static int updateOOMScoreAdj(int val, int prev, const char **err) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int updateRequirePass(sds val, sds prev, const char **err) {
|
||||||
|
UNUSED(prev);
|
||||||
|
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(val);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_OPENSSL
|
#ifdef USE_OPENSSL
|
||||||
static int updateTlsCfg(char *val, char *prev, const char **err) {
|
static int updateTlsCfg(char *val, char *prev, const char **err) {
|
||||||
UNUSED(val);
|
UNUSED(val);
|
||||||
@ -2414,6 +2546,27 @@ static int updateTlsCfgInt(long long val, long long prev, const char **err) {
|
|||||||
UNUSED(prev);
|
UNUSED(prev);
|
||||||
return updateTlsCfg(NULL, NULL, err);
|
return updateTlsCfg(NULL, NULL, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int updateTLSPort(long long val, long long prev, const char **err) {
|
||||||
|
/* Do nothing if port is unchanged */
|
||||||
|
if (val == prev) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Configure TLS if tls is enabled */
|
||||||
|
if (prev == 0 && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
|
||||||
|
*err = "Unable to update TLS configuration. Check server logs.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
asdsa
|
||||||
|
if (changeListenPort(val, &server.tlsfd, acceptTLSHandler) == C_ERR) {
|
||||||
|
*err = "Unable to listen on this port. Check server logs.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* USE_OPENSSL */
|
#endif /* USE_OPENSSL */
|
||||||
|
|
||||||
int fDummy = false;
|
int fDummy = false;
|
||||||
@ -2429,11 +2582,13 @@ standardConfig configs[] = {
|
|||||||
createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_del_sync_files, 0, NULL, NULL),
|
createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_del_sync_files, 0, NULL, NULL),
|
||||||
createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, g_pserver->activerehashing, 1, NULL, NULL),
|
createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, g_pserver->activerehashing, 1, NULL, NULL),
|
||||||
createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, g_pserver->stop_writes_on_bgsave_err, 1, NULL, NULL),
|
createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, g_pserver->stop_writes_on_bgsave_err, 1, NULL, NULL),
|
||||||
|
createBoolConfig("set-proc-title", NULL, IMMUTABLE_CONFIG, cserver.set_proc_title, 1, NULL, NULL), /* Should setproctitle be used? */
|
||||||
createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, g_pserver->dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
|
createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, g_pserver->dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
|
||||||
createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_eviction, 0, NULL, NULL),
|
createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_eviction, 0, NULL, NULL),
|
||||||
createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_expire, 0, NULL, NULL),
|
createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_expire, 0, NULL, NULL),
|
||||||
createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_server_del, 0, NULL, NULL),
|
createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_server_del, 0, NULL, NULL),
|
||||||
createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_user_del , 0, NULL, NULL),
|
createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_user_del , 0, NULL, NULL),
|
||||||
|
createBoolConfig("lazyfree-lazy-user-flush", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_user_flush , 0, NULL, NULL),
|
||||||
createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, g_pserver->repl_disable_tcp_nodelay, 0, NULL, NULL),
|
createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, g_pserver->repl_disable_tcp_nodelay, 0, NULL, NULL),
|
||||||
createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, g_pserver->repl_diskless_sync, 0, NULL, NULL),
|
createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, g_pserver->repl_diskless_sync, 0, NULL, NULL),
|
||||||
createBoolConfig("aof-rewrite-incremental-fsync", NULL, MODIFIABLE_CONFIG, g_pserver->aof_rewrite_incremental_fsync, 1, NULL, NULL),
|
createBoolConfig("aof-rewrite-incremental-fsync", NULL, MODIFIABLE_CONFIG, g_pserver->aof_rewrite_incremental_fsync, 1, NULL, NULL),
|
||||||
@ -2454,9 +2609,11 @@ standardConfig configs[] = {
|
|||||||
createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, g_pserver->cluster_enabled, 0, NULL, NULL),
|
createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, g_pserver->cluster_enabled, 0, NULL, NULL),
|
||||||
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, g_pserver->aof_enabled, 0, NULL, updateAppendonly),
|
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, g_pserver->aof_enabled, 0, NULL, updateAppendonly),
|
||||||
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, g_pserver->cluster_allow_reads_when_down, 0, NULL, NULL),
|
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, g_pserver->cluster_allow_reads_when_down, 0, NULL, NULL),
|
||||||
createBoolConfig("multi-master-no-forward", NULL, MODIFIABLE_CONFIG, cserver.multimaster_no_forward, 0, validateMultiMasterNoForward, NULL),
|
|
||||||
createBoolConfig("allow-write-during-load", NULL, MODIFIABLE_CONFIG, g_pserver->fWriteDuringActiveLoad, 0, NULL, NULL),
|
|
||||||
createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, fDummy, 0, NULL, NULL),
|
createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, fDummy, 0, NULL, NULL),
|
||||||
|
createBoolConfig("crash-log-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->crashlog_enabled, 1, NULL, updateSighandlerEnabled),
|
||||||
|
createBoolConfig("crash-memcheck-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->memcheck_enabled, 1, NULL, NULL),
|
||||||
|
createBoolConfig("use-exit-on-panic", NULL, MODIFIABLE_CONFIG, g_pserver->use_exit_on_panic, 0, NULL, NULL),
|
||||||
|
createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, g_pserver->disable_thp, 1, NULL, NULL),
|
||||||
|
|
||||||
/* String Configs */
|
/* String Configs */
|
||||||
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->acl_filename, "", NULL, NULL),
|
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->acl_filename, "", NULL, NULL),
|
||||||
@ -2464,7 +2621,6 @@ standardConfig configs[] = {
|
|||||||
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.pidfile, NULL, NULL, NULL),
|
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.pidfile, NULL, NULL, NULL),
|
||||||
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->slave_announce_ip, NULL, NULL, NULL),
|
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->slave_announce_ip, NULL, NULL, NULL),
|
||||||
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masteruser, NULL, NULL, updateMasterAuthConfig),
|
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masteruser, NULL, NULL, updateMasterAuthConfig),
|
||||||
createStringConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masterauth, NULL, NULL, updateMasterAuthConfig),
|
|
||||||
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->cluster_announce_ip, NULL, NULL, NULL),
|
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->cluster_announce_ip, NULL, NULL, NULL),
|
||||||
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL),
|
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL),
|
||||||
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL),
|
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL),
|
||||||
@ -2473,7 +2629,12 @@ standardConfig configs[] = {
|
|||||||
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bio_cpulist, NULL, NULL, NULL),
|
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bio_cpulist, NULL, NULL, NULL),
|
||||||
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->aof_rewrite_cpulist, NULL, NULL, NULL),
|
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->aof_rewrite_cpulist, NULL, NULL, NULL),
|
||||||
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bgsave_cpulist, NULL, NULL, NULL),
|
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bgsave_cpulist, NULL, NULL, NULL),
|
||||||
createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->ignore_warnings, "ARM64-COW-BUG", NULL, NULL),
|
createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->ignore_warnings, "", NULL, NULL),
|
||||||
|
createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, cserver.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate),
|
||||||
|
|
||||||
|
/* SDS Configs */
|
||||||
|
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masterauth, NULL, NULL, updateMasterAuthConfig),
|
||||||
|
createSDSConfig("requirepass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->requirepass, NULL, NULL, updateRequirePass),
|
||||||
|
|
||||||
/* Enum Configs */
|
/* Enum Configs */
|
||||||
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, cserver.supervised_mode, SUPERVISED_NONE, NULL, NULL),
|
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, cserver.supervised_mode, SUPERVISED_NONE, NULL, NULL),
|
||||||
@ -2483,10 +2644,12 @@ standardConfig configs[] = {
|
|||||||
createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, g_pserver->maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
|
createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, g_pserver->maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
|
||||||
createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, g_pserver->aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL),
|
createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, g_pserver->aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL),
|
||||||
createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, g_pserver->oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj),
|
createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, g_pserver->oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj),
|
||||||
|
createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, g_pserver->acl_pubusub_default, USER_FLAG_ALLCHANNELS, NULL, NULL),
|
||||||
|
createEnumConfig("sanitize-dump-payload", NULL, MODIFIABLE_CONFIG, sanitize_dump_payload_enum, cserver.sanitize_dump_payload, SANITIZE_DUMP_NO, NULL, NULL),
|
||||||
|
|
||||||
/* Integer configs */
|
/* Integer configs */
|
||||||
createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, cserver.dbnum, 16, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, cserver.dbnum, 16, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("port", NULL, IMMUTABLE_CONFIG, 0, 65535, g_pserver->port, 6379, INTEGER_CONFIG, NULL, NULL), /* TCP port. */
|
createIntConfig("port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->port, 6379, INTEGER_CONFIG, NULL, updatePort), /* TCP port. */
|
||||||
createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */
|
createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */
|
||||||
createIntConfig("list-max-ziplist-size", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->list_max_ziplist_size, -2, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("list-max-ziplist-size", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->list_max_ziplist_size, -2, INTEGER_CONFIG, NULL, NULL),
|
||||||
@ -2501,6 +2664,7 @@ standardConfig configs[] = {
|
|||||||
createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->slave_priority, 100, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->slave_priority, 100, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL),
|
||||||
|
createIntConfig("maxmemory-eviction-tenacity", NULL, MODIFIABLE_CONFIG, 0, 100, g_pserver->maxmemory_eviction_tenacity, 10, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, cserver.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */
|
createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, cserver.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */
|
||||||
createIntConfig("replica-announce-port", "slave-announce-port", MODIFIABLE_CONFIG, 0, 65535, g_pserver->slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("replica-announce-port", "slave-announce-port", MODIFIABLE_CONFIG, 0, 65535, g_pserver->slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, g_pserver->tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
|
createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, g_pserver->tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
|
||||||
@ -2509,8 +2673,8 @@ standardConfig configs[] = {
|
|||||||
createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_timeout, 60, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_timeout, 60, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->key_load_delay, 0, INTEGER_CONFIG, NULL, NULL),
|
createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->key_load_delay, 0, INTEGER_CONFIG, NULL, NULL),
|
||||||
createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, cserver.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */
|
createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, cserver.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */
|
||||||
createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ),
|
createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ),
|
||||||
createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves),
|
createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves),
|
||||||
@ -2522,7 +2686,6 @@ standardConfig configs[] = {
|
|||||||
createUIntConfig("loading-process-events-interval-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_keys, 8192, MEMORY_CONFIG, NULL, NULL),
|
createUIntConfig("loading-process-events-interval-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_keys, 8192, MEMORY_CONFIG, NULL, NULL),
|
||||||
|
|
||||||
/* Unsigned Long configs */
|
/* Unsigned Long configs */
|
||||||
createULongConfig("loading-process-events-interval-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_bytes, 2*1024*1024, MEMORY_CONFIG, NULL, NULL),
|
|
||||||
createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, cserver.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */
|
createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, cserver.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, g_pserver->slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
|
createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
|
||||||
createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
|
createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
|
||||||
@ -2549,28 +2712,36 @@ standardConfig configs[] = {
|
|||||||
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
||||||
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
|
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
|
||||||
createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */
|
createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */
|
||||||
|
createSizeTConfig("client-query-buffer-limit", NULL, MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, cserver.client_max_querybuf_len, 1024*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Default: 1GB max query buffer. */
|
||||||
|
|
||||||
/* Other configs */
|
/* Other configs */
|
||||||
createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */
|
createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->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, g_pserver->aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL),
|
createOffTConfig("auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL),
|
||||||
|
|
||||||
|
/* KeyDB Specific Configs */
|
||||||
|
createULongConfig("loading-process-events-interval-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_bytes, 2*1024*1024, MEMORY_CONFIG, NULL, NULL),
|
||||||
|
createBoolConfig("multi-master-no-forward", NULL, MODIFIABLE_CONFIG, cserver.multimaster_no_forward, 0, validateMultiMasterNoForward, NULL),
|
||||||
|
createBoolConfig("allow-write-during-load", NULL, MODIFIABLE_CONFIG, g_pserver->fWriteDuringActiveLoad, 0, NULL, NULL),
|
||||||
|
|
||||||
#ifdef USE_OPENSSL
|
#ifdef USE_OPENSSL
|
||||||
createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, g_pserver->tls_port, 0, INTEGER_CONFIG, NULL, NULL), /* TCP port. */
|
createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, updateTLSPort), /* TCP port. */
|
||||||
createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, updateTlsCfgInt),
|
createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, updateTlsCfgInt),
|
||||||
createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, updateTlsCfgInt),
|
createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, updateTlsCfgInt),
|
||||||
createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, g_pserver->tls_cluster, 0, NULL, NULL),
|
createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, updateTlsCfgBool),
|
||||||
createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, g_pserver->tls_replication, 0, NULL, NULL),
|
createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, updateTlsCfgBool),
|
||||||
createEnumConfig("tls-auth-clients", NULL, MODIFIABLE_CONFIG, tls_auth_clients_enum, g_pserver->tls_auth_clients, TLS_CLIENT_AUTH_YES, NULL, NULL),
|
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, g_pserver->tls_ctx_config.prefer_server_ciphers, 0, NULL, updateTlsCfgBool),
|
createBoolConfig("tls-prefer-server-ciphers", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.prefer_server_ciphers, 0, NULL, updateTlsCfgBool),
|
||||||
createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, g_pserver->tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool),
|
createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool),
|
||||||
createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg),
|
createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg),
|
||||||
createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.key_file, NULL, NULL, updateTlsCfg),
|
createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg),
|
||||||
createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg),
|
createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, updateTlsCfg),
|
||||||
createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg),
|
createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, updateTlsCfg),
|
||||||
createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg),
|
createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg),
|
||||||
createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.protocols, NULL, NULL, updateTlsCfg),
|
createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg),
|
||||||
createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.ciphers, NULL, NULL, updateTlsCfg),
|
createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg),
|
||||||
createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.ciphersuites, NULL, NULL, updateTlsCfg),
|
createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.protocols, NULL, NULL, updateTlsCfg),
|
||||||
|
createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphers, NULL, NULL, updateTlsCfg),
|
||||||
|
createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphersuites, NULL, NULL, updateTlsCfg),
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* NULL Terminator */
|
/* NULL Terminator */
|
||||||
@ -2590,12 +2761,17 @@ void configCommand(client *c) {
|
|||||||
|
|
||||||
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
|
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"GET <pattern> -- Return parameters matching the glob-like <pattern> and their values.",
|
"GET <pattern>",
|
||||||
"SET <parameter> <value> -- Set parameter to value.",
|
" Return parameters matching the glob-like <pattern> and their values.",
|
||||||
"RESETSTAT -- Reset statistics reported by INFO.",
|
"SET <directive> <value>",
|
||||||
"REWRITE -- Rewrite the configuration file.",
|
" Set the configuration <directive> to <value>.",
|
||||||
|
"RESETSTAT",
|
||||||
|
" Reset statistics reported by the INFO command.",
|
||||||
|
"REWRITE",
|
||||||
|
" Rewrite the configuration file.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"set") && c->argc == 4) {
|
} else if (!strcasecmp(szFromObj(c->argv[1]),"set") && c->argc == 4) {
|
||||||
configSetCommand(c);
|
configSetCommand(c);
|
||||||
@ -2604,6 +2780,7 @@ NULL
|
|||||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"resetstat") && c->argc == 2) {
|
} else if (!strcasecmp(szFromObj(c->argv[1]),"resetstat") && c->argc == 2) {
|
||||||
resetServerStats();
|
resetServerStats();
|
||||||
resetCommandTableStats();
|
resetCommandTableStats();
|
||||||
|
resetErrorTableStats();
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"rewrite") && c->argc == 2) {
|
} else if (!strcasecmp(szFromObj(c->argv[1]),"rewrite") && c->argc == 2) {
|
||||||
if (cserver.configfile == NULL) {
|
if (cserver.configfile == NULL) {
|
||||||
|
40
src/config.h
40
src/config.h
@ -35,10 +35,11 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <linux/version.h>
|
|
||||||
#include <features.h>
|
#include <features.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb"
|
||||||
|
|
||||||
/* Define redis_fstat to fstat or fstat64() */
|
/* Define redis_fstat to fstat or fstat64() */
|
||||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||||
#define redis_fstat fstat64
|
#define redis_fstat fstat64
|
||||||
@ -65,7 +66,7 @@
|
|||||||
/* Test for backtrace() */
|
/* Test for backtrace() */
|
||||||
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
|
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
|
||||||
defined(__FreeBSD__) || ((defined(__OpenBSD__) || defined(__NetBSD__)) && defined(USE_BACKTRACE))\
|
defined(__FreeBSD__) || ((defined(__OpenBSD__) || defined(__NetBSD__)) && defined(USE_BACKTRACE))\
|
||||||
|| defined(__DragonFly__)
|
|| defined(__DragonFly__) || (defined(__UCLIBC__) && defined(__UCLIBC_HAS_BACKTRACE__))
|
||||||
#define HAVE_BACKTRACE 1
|
#define HAVE_BACKTRACE 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -87,6 +88,7 @@
|
|||||||
#include <sys/feature_tests.h>
|
#include <sys/feature_tests.h>
|
||||||
#ifdef _DTRACE_VERSION
|
#ifdef _DTRACE_VERSION
|
||||||
#define HAVE_EVPORT 1
|
#define HAVE_EVPORT 1
|
||||||
|
#define HAVE_PSINFO 1
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -97,21 +99,23 @@
|
|||||||
#define redis_fsync fsync
|
#define redis_fsync fsync
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
|
#if __GNUC__ >= 4
|
||||||
* the plain fsync() call. */
|
#define redis_unreachable __builtin_unreachable
|
||||||
#ifdef __linux__
|
|
||||||
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
|
|
||||||
#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6))
|
|
||||||
#define HAVE_SYNC_FILE_RANGE 1
|
|
||||||
#endif
|
|
||||||
#else
|
#else
|
||||||
#if (LINUX_VERSION_CODE >= 0x020611)
|
#define redis_unreachable abort
|
||||||
#define HAVE_SYNC_FILE_RANGE 1
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_SYNC_FILE_RANGE
|
#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
|
||||||
|
|
||||||
|
/* 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 rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE)
|
#define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE)
|
||||||
#else
|
#else
|
||||||
#define rdb_fsync_range(fd,off,size) fsync(fd)
|
#define rdb_fsync_range(fd,off,size) fsync(fd)
|
||||||
@ -128,7 +132,7 @@
|
|||||||
#define ESOCKTNOSUPPORT 0
|
#define ESOCKTNOSUPPORT 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
|
#if (defined __linux || defined __APPLE__)
|
||||||
#define USE_SETPROCTITLE
|
#define USE_SETPROCTITLE
|
||||||
#define INIT_SETPROCTITLE_REPLACEMENT
|
#define INIT_SETPROCTITLE_REPLACEMENT
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@ -247,11 +251,11 @@ void setproctitle(const char *fmt, ...);
|
|||||||
#elif defined __NetBSD__
|
#elif defined __NetBSD__
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), "%s", name)
|
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), "%s", name)
|
||||||
|
#elif defined __HAIKU__
|
||||||
|
#include <kernel/OS.h>
|
||||||
|
#define redis_set_thread_title(name) rename_thread(find_thread(0), name)
|
||||||
#else
|
#else
|
||||||
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
|
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
#endif
|
|
||||||
int pthread_setname_np(const char *name);
|
int pthread_setname_np(const char *name);
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#define redis_set_thread_title(name) pthread_setname_np(name)
|
#define redis_set_thread_title(name) pthread_setname_np(name)
|
||||||
|
@ -147,8 +147,7 @@ void *connGetPrivateData(connection *conn) {
|
|||||||
/* Close the connection and free resources. */
|
/* Close the connection and free resources. */
|
||||||
static void connSocketClose(connection *conn) {
|
static void connSocketClose(connection *conn) {
|
||||||
if (conn->fd != -1) {
|
if (conn->fd != -1) {
|
||||||
aeDeleteFileEvent(serverTL->el,conn->fd,AE_READABLE);
|
aeDeleteFileEvent(serverTL->el,conn->fd,AE_READABLE | AE_WRITABLE);
|
||||||
aeDeleteFileEvent(serverTL->el,conn->fd,AE_WRITABLE);
|
|
||||||
close(conn->fd);
|
close(conn->fd);
|
||||||
conn->fd = -1;
|
conn->fd = -1;
|
||||||
}
|
}
|
||||||
@ -384,15 +383,15 @@ int connGetSocketError(connection *conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
|
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
|
||||||
return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port);
|
return anetFdToString(conn ? conn->fd : -1, ip, ip_len, port, FD_TO_PEER_NAME);
|
||||||
}
|
|
||||||
|
|
||||||
int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
|
|
||||||
return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
|
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
|
||||||
return anetSockName(conn->fd, ip, ip_len, port);
|
return anetFdToString(conn->fd, ip, ip_len, port, FD_TO_SOCK_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connFormatFdAddr(connection *conn, char *buf, size_t buf_len, int fd_to_str_type) {
|
||||||
|
return anetFormatFdAddr(conn ? conn->fd : -1, buf, buf_len, fd_to_str_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
int connBlock(connection *conn) {
|
int connBlock(connection *conn) {
|
||||||
@ -447,7 +446,7 @@ void connSetThreadAffinity(connection *conn, int cpu) {
|
|||||||
* For sockets, we always return "fd=<fdnum>" to maintain compatibility.
|
* For sockets, we always return "fd=<fdnum>" to maintain compatibility.
|
||||||
*/
|
*/
|
||||||
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
|
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
|
||||||
snprintf(buf, buf_len-1, "fd=%i", conn->fd);
|
snprintf(buf, buf_len-1, "fd=%i", conn == NULL ? -1 : conn->fd);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user