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
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
about: Help us improve KeyDB by reporting a bug
|
||||
title: '[BUG]'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
** Log Files **
|
||||
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 ==="
|
||||
A short description of the bug.
|
||||
|
||||
Please copy everything after this line.
|
||||
**To reproduce**
|
||||
|
||||
**To Reproduce**
|
||||
Do you know how to reproduce this? If so please provide repro steps.
|
||||
Steps to reproduce the behavior and/or a minimal code sample.
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
A description of what you expected to happen.
|
||||
|
||||
**Additional information**
|
||||
|
||||
Any additional information that is relevant to the problem.
|
||||
|
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
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
about: Suggest a feature for KeyDB
|
||||
title: '[NEW]'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
**The problem/use-case that the feature addresses**
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
A description of the problem that the feature will solve, or the use-case with which the feature will be used.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
**Description of the feature**
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
A description of what you want to happen.
|
||||
|
||||
**Alternatives you've considered**
|
||||
|
||||
Any alternative solutions or features you've considered, including references to existing open and closed feature requests in this repository.
|
||||
|
||||
**Additional information**
|
||||
|
||||
Any additional information that is relevant to the feature request.
|
||||
|
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: |
|
||||
sudo apt-get update
|
||||
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
|
||||
run: ./utils/gen-test-certs.sh
|
||||
- name: test-tls
|
||||
@ -44,7 +44,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make -j2
|
||||
run: make REDIS_CFLAGS='-Werror' -j2
|
||||
|
||||
build-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
@ -54,5 +54,5 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
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-cache/*
|
||||
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) 2019-2020, John Sully
|
||||
Copyright (C) 2019-2021, John Sully
|
||||
Copyright (C) 2020-2021, EQ Alpha Technology Ltd.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
19
README.md
19
README.md
@ -118,6 +118,13 @@ installed):
|
||||
% ./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
|
||||
---------
|
||||
|
||||
@ -169,6 +176,18 @@ To compile against jemalloc on Mac OS X systems, use:
|
||||
|
||||
% make MALLOC=jemalloc
|
||||
|
||||
Monotonic clock
|
||||
---------------
|
||||
|
||||
By default, Redis will build using the POSIX clock_gettime function as the
|
||||
monotonic clock source. On most modern systems, the internal processor clock
|
||||
can be used to improve performance. Cautions can be found here:
|
||||
http://oliveryang.net/2015/09/pitfalls-of-TSC-usage/
|
||||
|
||||
To build with support for the processor's internal instruction clock, use:
|
||||
|
||||
% make CFLAGS="-DUSE_PROCESSOR_CLOCK"
|
||||
|
||||
Verbose build
|
||||
-------------
|
||||
|
||||
|
13
deps/Makefile
vendored
13
deps/Makefile
vendored
@ -37,6 +37,7 @@ distclean:
|
||||
-(cd linenoise && $(MAKE) clean) > /dev/null || true
|
||||
-(cd lua && $(MAKE) clean) > /dev/null || true
|
||||
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
|
||||
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
|
||||
-(rm -f .make-*)
|
||||
|
||||
.PHONY: distclean
|
||||
@ -62,18 +63,24 @@ memkind:
|
||||
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)
|
||||
# Make isinf() available
|
||||
LUA_CFLAGS= -D__C99FEATURES__=1
|
||||
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's Makefile defines AR="ar rcu", which is unusual, and makes it more
|
||||
# challenging to cross-compile lua (and redis). These defines make it easier
|
||||
# to fit redis into cross-compilation environments, which typically set AR.
|
||||
AR=ar
|
||||
ARFLAGS=rcu
|
||||
ARFLAGS=rc
|
||||
|
||||
lua: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
@ -86,7 +93,7 @@ JEMALLOC_LDFLAGS= $(LDFLAGS)
|
||||
|
||||
jemalloc: .make-prerequisites
|
||||
@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
|
||||
|
||||
.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
|
||||
against supports such Redis-specific modifications. So in theory, if you
|
||||
are not interested in the active defragmentation, you can replace Jemalloc
|
||||
just following tose steps:
|
||||
just following these steps:
|
||||
|
||||
1. Remove the jemalloc directory.
|
||||
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
|
||||
/*.pc
|
||||
*.dSYM
|
||||
tags
|
||||
|
52
deps/hiredis/.travis.yml
vendored
52
deps/hiredis/.travis.yml
vendored
@ -1,5 +1,4 @@
|
||||
language: c
|
||||
sudo: false
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
@ -8,17 +7,34 @@ os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
dist: bionic
|
||||
|
||||
branches:
|
||||
only:
|
||||
- staging
|
||||
- trying
|
||||
- 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:
|
||||
- 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:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:chris-lea/redis-server'
|
||||
packages:
|
||||
- libc6-dbg
|
||||
- libc6-dev
|
||||
@ -27,14 +43,20 @@ addons:
|
||||
- libc6-dbg:i386
|
||||
- gcc-multilib
|
||||
- g++-multilib
|
||||
- libssl-dev
|
||||
- libssl-dev:i386
|
||||
- valgrind
|
||||
- redis
|
||||
|
||||
env:
|
||||
- BITS="32"
|
||||
- BITS="64"
|
||||
|
||||
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 [ "$BITS" == "32" ]; then
|
||||
CFLAGS="-m32 -Werror";
|
||||
@ -58,12 +80,24 @@ script:
|
||||
fi;
|
||||
fi;
|
||||
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
|
||||
- make && make clean;
|
||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
if [ "$BITS" == "64" ]; then
|
||||
OPENSSL_PREFIX="$(ls -d /usr/local/Cellar/openssl@1.1/*)" USE_SSL=1 make;
|
||||
fi;
|
||||
else
|
||||
USE_SSL=1 make;
|
||||
fi;
|
||||
- mkdir build/ && cd build/
|
||||
- cmake .. ${EXTRA_CMAKE_OPTS}
|
||||
- make VERBOSE=1
|
||||
- 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:
|
||||
# Windows MinGW cross compile on Linux
|
||||
- os: linux
|
||||
@ -89,9 +123,9 @@ matrix:
|
||||
- eval "${MATRIX_EVAL}"
|
||||
install:
|
||||
- choco install ninja
|
||||
- choco install -y memurai-developer
|
||||
script:
|
||||
- mkdir build && cd build
|
||||
- cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 &&
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release &&
|
||||
ninja -v'
|
||||
- ctest -V
|
||||
- cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&'
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v
|
||||
- ./hiredis-test.exe
|
||||
|
182
deps/hiredis/CHANGELOG.md
vendored
182
deps/hiredis/CHANGELOG.md
vendored
@ -1,28 +1,175 @@
|
||||
### 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**:
|
||||
|
||||
* 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`.
|
||||
* `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))
|
||||
|
||||
* 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.
|
||||
|
||||
### 0.x.x (unreleased)
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well.
|
||||
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
|
||||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent
|
||||
with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`.
|
||||
|
||||
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
|
||||
|
||||
**New features:**
|
||||
- Support for RESP3
|
||||
[\#697](https://github.com/redis/hiredis/pull/697),
|
||||
[\#805](https://github.com/redis/hiredis/pull/805),
|
||||
[\#819](https://github.com/redis/hiredis/pull/819),
|
||||
[\#841](https://github.com/redis/hiredis/pull/841)
|
||||
([Yossi Gottlieb](https://github.com/yossigo), [Michael Grunder](https://github.com/michael-grunder))
|
||||
- Support for SSL connections
|
||||
[\#645](https://github.com/redis/hiredis/pull/645),
|
||||
[\#699](https://github.com/redis/hiredis/pull/699),
|
||||
[\#702](https://github.com/redis/hiredis/pull/702),
|
||||
[\#708](https://github.com/redis/hiredis/pull/708),
|
||||
[\#711](https://github.com/redis/hiredis/pull/711),
|
||||
[\#821](https://github.com/redis/hiredis/pull/821),
|
||||
[more](https://github.com/redis/hiredis/pulls?q=is%3Apr+is%3Amerged+SSL)
|
||||
([Mark Nunberg](https://github.com/mnunberg), [Yossi Gottlieb](https://github.com/yossigo))
|
||||
- Run-time allocator injection
|
||||
[\#800](https://github.com/redis/hiredis/pull/800)
|
||||
([Michael Grunder](https://github.com/michael-grunder))
|
||||
- Improved Windows support (including MinGW and Windows CI)
|
||||
[\#652](https://github.com/redis/hiredis/pull/652),
|
||||
[\#663](https://github.com/redis/hiredis/pull/663)
|
||||
([Marcus Geelnard](https://www.bitsnbites.eu/author/m/))
|
||||
- Adds support for distinct connect and command timeouts
|
||||
[\#839](https://github.com/redis/hiredis/pull/839),
|
||||
[\#829](https://github.com/redis/hiredis/pull/829)
|
||||
([Valentino Geron](https://github.com/valentinogeron))
|
||||
- Add generic pointer and destructor to `redisContext` that users can use for context.
|
||||
[\#855](https://github.com/redis/hiredis/pull/855)
|
||||
([Michael Grunder](https://github.com/michael-grunder))
|
||||
|
||||
**Closed issues (that involved code changes):**
|
||||
|
||||
- Makefile does not install TLS libraries [\#809](https://github.com/redis/hiredis/issues/809)
|
||||
- redisConnectWithOptions should not set command timeout [\#722](https://github.com/redis/hiredis/issues/722), [\#829](https://github.com/redis/hiredis/pull/829) ([valentinogeron](https://github.com/valentinogeron))
|
||||
- Fix integer overflow in `sdsrange` [\#827](https://github.com/redis/hiredis/issues/827)
|
||||
- INFO & CLUSTER commands failed when using RESP3 [\#802](https://github.com/redis/hiredis/issues/802)
|
||||
- Windows compatibility patches [\#687](https://github.com/redis/hiredis/issues/687), [\#838](https://github.com/redis/hiredis/issues/838), [\#842](https://github.com/redis/hiredis/issues/842)
|
||||
- RESP3 PUSH messages incorrectly use pending callback [\#825](https://github.com/redis/hiredis/issues/825)
|
||||
- Asynchronous PSUBSCRIBE command fails when using RESP3 [\#815](https://github.com/redis/hiredis/issues/815)
|
||||
- New SSL API [\#804](https://github.com/redis/hiredis/issues/804), [\#813](https://github.com/redis/hiredis/issues/813)
|
||||
- Hard-coded limit of nested reply depth [\#794](https://github.com/redis/hiredis/issues/794)
|
||||
- Fix TCP_NODELAY in Windows/OSX [\#679](https://github.com/redis/hiredis/issues/679), [\#690](https://github.com/redis/hiredis/issues/690), [\#779](https://github.com/redis/hiredis/issues/779), [\#785](https://github.com/redis/hiredis/issues/785),
|
||||
- Added timers to libev adapter. [\#778](https://github.com/redis/hiredis/issues/778), [\#795](https://github.com/redis/hiredis/pull/795)
|
||||
- Initialization discards const qualifier [\#777](https://github.com/redis/hiredis/issues/777)
|
||||
- \[BUG\]\[MinGW64\] Error setting socket timeout [\#775](https://github.com/redis/hiredis/issues/775)
|
||||
- undefined reference to hi_malloc [\#769](https://github.com/redis/hiredis/issues/769)
|
||||
- hiredis pkg-config file incorrectly ignores multiarch libdir spec'n [\#767](https://github.com/redis/hiredis/issues/767)
|
||||
- Don't use -G to build shared object on Solaris [\#757](https://github.com/redis/hiredis/issues/757)
|
||||
- error when make USE\_SSL=1 [\#748](https://github.com/redis/hiredis/issues/748)
|
||||
- Allow to change SSL Mode [\#646](https://github.com/redis/hiredis/issues/646)
|
||||
- hiredis/adapters/libevent.h memleak [\#618](https://github.com/redis/hiredis/issues/618)
|
||||
- redisLibuvPoll crash when server closes the connetion [\#545](https://github.com/redis/hiredis/issues/545)
|
||||
- about redisAsyncDisconnect question [\#518](https://github.com/redis/hiredis/issues/518)
|
||||
- hiredis adapters libuv error for help [\#508](https://github.com/redis/hiredis/issues/508)
|
||||
- API/ABI changes analysis [\#506](https://github.com/redis/hiredis/issues/506)
|
||||
- Memory leak patch in Redis [\#502](https://github.com/redis/hiredis/issues/502)
|
||||
- Remove the depth limitation [\#421](https://github.com/redis/hiredis/issues/421)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Move SSL management to a distinct private pointer [\#855](https://github.com/redis/hiredis/pull/855) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Move include to sockcompat.h to maintain style [\#850](https://github.com/redis/hiredis/pull/850) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Remove erroneous tag and add license to push example [\#849](https://github.com/redis/hiredis/pull/849) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- fix windows compiling with mingw [\#848](https://github.com/redis/hiredis/pull/848) ([rmalizia44](https://github.com/rmalizia44))
|
||||
- Some Windows quality of life improvements. [\#846](https://github.com/redis/hiredis/pull/846) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Use \_WIN32 define instead of WIN32 [\#845](https://github.com/redis/hiredis/pull/845) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Non Linux CI fixes [\#844](https://github.com/redis/hiredis/pull/844) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Resp3 oob push support [\#841](https://github.com/redis/hiredis/pull/841) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- fix \#785: defer TCP\_NODELAY in async tcp connections [\#836](https://github.com/redis/hiredis/pull/836) ([OmriSteiner](https://github.com/OmriSteiner))
|
||||
- sdsrange overflow fix [\#830](https://github.com/redis/hiredis/pull/830) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Use explicit pointer casting for c++ compatibility [\#826](https://github.com/redis/hiredis/pull/826) ([aureus1](https://github.com/aureus1))
|
||||
- Document allocator injection and completeness fix in test.c [\#824](https://github.com/redis/hiredis/pull/824) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Use unique names for allocator struct members [\#823](https://github.com/redis/hiredis/pull/823) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- New SSL API to replace redisSecureConnection\(\). [\#821](https://github.com/redis/hiredis/pull/821) ([yossigo](https://github.com/yossigo))
|
||||
- Add logic to handle RESP3 push messages [\#819](https://github.com/redis/hiredis/pull/819) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Use standrad isxdigit instead of custom helper function. [\#814](https://github.com/redis/hiredis/pull/814) ([tryfinally](https://github.com/tryfinally))
|
||||
- Fix missing SSL build/install options. [\#812](https://github.com/redis/hiredis/pull/812) ([yossigo](https://github.com/yossigo))
|
||||
- Add link to ABI tracker [\#808](https://github.com/redis/hiredis/pull/808) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Resp3 verbatim string support [\#805](https://github.com/redis/hiredis/pull/805) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Allow users to replace allocator and handle OOM everywhere. [\#800](https://github.com/redis/hiredis/pull/800) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Remove nested depth limitation. [\#797](https://github.com/redis/hiredis/pull/797) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Attempt to fix compilation on Solaris [\#796](https://github.com/redis/hiredis/pull/796) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Support timeouts in libev adapater [\#795](https://github.com/redis/hiredis/pull/795) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Fix pkgconfig when installing to a custom lib dir [\#793](https://github.com/redis/hiredis/pull/793) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Fix USE\_SSL=1 make/cmake on OSX and CMake tests [\#789](https://github.com/redis/hiredis/pull/789) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Use correct libuv call on Windows [\#784](https://github.com/redis/hiredis/pull/784) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Added CMake package config and fixed hiredis\_ssl on Windows [\#783](https://github.com/redis/hiredis/pull/783) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- CMake: Set hiredis\_ssl shared object version. [\#780](https://github.com/redis/hiredis/pull/780) ([yossigo](https://github.com/yossigo))
|
||||
- Win32 tests and timeout fix [\#776](https://github.com/redis/hiredis/pull/776) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Provides an optional cleanup callback for async data. [\#768](https://github.com/redis/hiredis/pull/768) ([heronr](https://github.com/heronr))
|
||||
- Housekeeping fixes [\#764](https://github.com/redis/hiredis/pull/764) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- install alloc.h [\#756](https://github.com/redis/hiredis/pull/756) ([ch1aki](https://github.com/ch1aki))
|
||||
- fix spelling mistakes [\#746](https://github.com/redis/hiredis/pull/746) ([ShooterIT](https://github.com/ShooterIT))
|
||||
- Free the reply in redisGetReply when passed NULL [\#741](https://github.com/redis/hiredis/pull/741) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Fix dead code in sslLogCallback relating to should\_log variable. [\#737](https://github.com/redis/hiredis/pull/737) ([natoscott](https://github.com/natoscott))
|
||||
- Fix typo in dict.c. [\#731](https://github.com/redis/hiredis/pull/731) ([Kevin-Xi](https://github.com/Kevin-Xi))
|
||||
- Adding an option to DISABLE\_TESTS [\#727](https://github.com/redis/hiredis/pull/727) ([pbotros](https://github.com/pbotros))
|
||||
- Update README with SSL support. [\#720](https://github.com/redis/hiredis/pull/720) ([yossigo](https://github.com/yossigo))
|
||||
- Fixes leaks in unit tests [\#715](https://github.com/redis/hiredis/pull/715) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- SSL Tests [\#711](https://github.com/redis/hiredis/pull/711) ([yossigo](https://github.com/yossigo))
|
||||
- SSL Reorganization [\#708](https://github.com/redis/hiredis/pull/708) ([yossigo](https://github.com/yossigo))
|
||||
- Fix MSVC build. [\#706](https://github.com/redis/hiredis/pull/706) ([yossigo](https://github.com/yossigo))
|
||||
- SSL: Properly report SSL\_connect\(\) errors. [\#702](https://github.com/redis/hiredis/pull/702) ([yossigo](https://github.com/yossigo))
|
||||
- Silent SSL trace to stdout by default. [\#699](https://github.com/redis/hiredis/pull/699) ([yossigo](https://github.com/yossigo))
|
||||
- Port RESP3 support from Redis. [\#697](https://github.com/redis/hiredis/pull/697) ([yossigo](https://github.com/yossigo))
|
||||
- Removed whitespace before newline [\#691](https://github.com/redis/hiredis/pull/691) ([Miniwoffer](https://github.com/Miniwoffer))
|
||||
- Add install adapters header files [\#688](https://github.com/redis/hiredis/pull/688) ([kevin1018](https://github.com/kevin1018))
|
||||
- Remove unnecessary null check before free [\#684](https://github.com/redis/hiredis/pull/684) ([qlyoung](https://github.com/qlyoung))
|
||||
- redisReaderGetReply leak memory [\#671](https://github.com/redis/hiredis/pull/671) ([movebean](https://github.com/movebean))
|
||||
- fix timeout code in windows [\#670](https://github.com/redis/hiredis/pull/670) ([jman-krafton](https://github.com/jman-krafton))
|
||||
- test: fix errstr matching for musl libc [\#665](https://github.com/redis/hiredis/pull/665) ([ghost](https://github.com/ghost))
|
||||
- Windows: MinGW fixes and Windows Travis builders [\#663](https://github.com/redis/hiredis/pull/663) ([mbitsnbites](https://github.com/mbitsnbites))
|
||||
- The setsockopt and getsockopt API diffs from BSD socket and WSA one [\#662](https://github.com/redis/hiredis/pull/662) ([dragonation](https://github.com/dragonation))
|
||||
- Fix Compile Error On Windows \(Visual Studio\) [\#658](https://github.com/redis/hiredis/pull/658) ([jinjiazhang](https://github.com/jinjiazhang))
|
||||
- Fix NXDOMAIN test case [\#653](https://github.com/redis/hiredis/pull/653) ([michael-grunder](https://github.com/michael-grunder))
|
||||
- Add MinGW support [\#652](https://github.com/redis/hiredis/pull/652) ([mbitsnbites](https://github.com/mbitsnbites))
|
||||
- SSL Support [\#645](https://github.com/redis/hiredis/pull/645) ([mnunberg](https://github.com/mnunberg))
|
||||
- Fix Invalid argument after redisAsyncConnectUnix [\#644](https://github.com/redis/hiredis/pull/644) ([codehz](https://github.com/codehz))
|
||||
- Makefile: use predefined AR [\#632](https://github.com/redis/hiredis/pull/632) ([Mic92](https://github.com/Mic92))
|
||||
- FreeBSD build fix [\#628](https://github.com/redis/hiredis/pull/628) ([devnexen](https://github.com/devnexen))
|
||||
- Fix errors not propagating properly with libuv.h. [\#624](https://github.com/redis/hiredis/pull/624) ([yossigo](https://github.com/yossigo))
|
||||
- Update README.md [\#621](https://github.com/redis/hiredis/pull/621) ([Crunsher](https://github.com/Crunsher))
|
||||
- Fix redisBufferRead documentation [\#620](https://github.com/redis/hiredis/pull/620) ([hacst](https://github.com/hacst))
|
||||
- Add CPPFLAGS to REAL\_CFLAGS [\#614](https://github.com/redis/hiredis/pull/614) ([thomaslee](https://github.com/thomaslee))
|
||||
- Update createArray to take size\_t [\#597](https://github.com/redis/hiredis/pull/597) ([justinbrewer](https://github.com/justinbrewer))
|
||||
- fix common realloc mistake and add null check more [\#580](https://github.com/redis/hiredis/pull/580) ([charsyam](https://github.com/charsyam))
|
||||
- Proper error reporting for connect failures [\#578](https://github.com/redis/hiredis/pull/578) ([mnunberg](https://github.com/mnunberg))
|
||||
|
||||
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
||||
|
||||
## [1.0.0-rc1](https://github.com/redis/hiredis/tree/v1.0.0-rc1) - (2020-07-29)
|
||||
|
||||
_Note: There were no changes to code between v1.0.0-rc1 and v1.0.0 so see v1.0.0 for changelog_
|
||||
|
||||
### 0.14.1 (2020-03-13)
|
||||
|
||||
* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder)
|
||||
|
||||
### 0.14.0 (2018-09-25)
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well.
|
||||
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
|
||||
|
||||
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
|
||||
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
|
||||
@ -196,4 +343,3 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
|
||||
### 0.10.0
|
||||
|
||||
* See commit log.
|
||||
|
||||
|
93
deps/hiredis/CMakeLists.txt
vendored
93
deps/hiredis/CMakeLists.txt
vendored
@ -3,6 +3,8 @@ INCLUDE(GNUInstallDirs)
|
||||
PROJECT(hiredis)
|
||||
|
||||
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)
|
||||
SET(VERSION_REGEX "^#define ${name} (.+)$")
|
||||
@ -22,7 +24,8 @@ PROJECT(hiredis VERSION "${VERSION}")
|
||||
|
||||
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
|
||||
|
||||
ADD_LIBRARY(hiredis SHARED
|
||||
SET(hiredis_sources
|
||||
alloc.c
|
||||
async.c
|
||||
dict.c
|
||||
hiredis.c
|
||||
@ -31,20 +34,32 @@ ADD_LIBRARY(hiredis SHARED
|
||||
sds.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
|
||||
PROPERTIES
|
||||
PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE
|
||||
VERSION "${HIREDIS_SONAME}")
|
||||
IF(WIN32 OR MINGW)
|
||||
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
INSTALL(DIRECTORY adapters
|
||||
@ -53,6 +68,26 @@ INSTALL(DIRECTORY adapters
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
export(EXPORT hiredis-targets
|
||||
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake"
|
||||
NAMESPACE hiredis::)
|
||||
|
||||
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 (NOT OPENSSL_ROOT_DIR)
|
||||
IF (APPLE)
|
||||
@ -60,26 +95,66 @@ IF(ENABLE_SSL)
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
FIND_PACKAGE(OpenSSL REQUIRED)
|
||||
ADD_LIBRARY(hiredis_ssl SHARED
|
||||
SET(hiredis_ssl_sources
|
||||
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_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)
|
||||
|
||||
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
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
export(EXPORT hiredis_ssl-targets
|
||||
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake"
|
||||
NAMESPACE hiredis::)
|
||||
|
||||
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()
|
||||
|
||||
IF(NOT (WIN32 OR MINGW))
|
||||
IF(NOT DISABLE_TESTS)
|
||||
ENABLE_TESTING()
|
||||
ADD_EXECUTABLE(hiredis-test test.c)
|
||||
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
|
||||
IF(ENABLE_SSL_TESTS)
|
||||
ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1)
|
||||
TARGET_LINK_LIBRARIES(hiredis-test hiredis hiredis_ssl)
|
||||
ELSE()
|
||||
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
|
||||
ENDIF()
|
||||
ADD_TEST(NAME hiredis-test
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
|
||||
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>
|
||||
# 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
|
||||
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)
|
||||
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
|
||||
endif
|
||||
TESTS=hiredis-test
|
||||
LIBNAME=libhiredis
|
||||
SSL_LIBNAME=libhiredis_ssl
|
||||
PKGCONFNAME=hiredis.pc
|
||||
SSL_LIBNAME=libhiredis_ssl
|
||||
SSL_PKGCONFNAME=hiredis_ssl.pc
|
||||
|
||||
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_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
|
||||
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
|
||||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
|
||||
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
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
@ -80,13 +85,22 @@ else
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
IS_SUN_CC=$(shell sh -c '$(CC) -V 2>&1 |egrep -i -c "sun|studio"')
|
||||
ifeq ($(IS_SUN_CC),1)
|
||||
SUN_SHARED_FLAG=-G
|
||||
else
|
||||
SUN_SHARED_FLAG=-shared
|
||||
endif
|
||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -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
|
||||
ifeq ($(uname_S),Darwin)
|
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
SSL_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) -o $(SSL_DYLIBNAME) $(LDFLAGS) $(SSL_LDFLAGS)
|
||||
DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup
|
||||
endif
|
||||
|
||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
||||
@ -95,15 +109,16 @@ all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
|
||||
endif
|
||||
|
||||
# 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
|
||||
dict.o: dict.c fmacros.h dict.h
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
|
||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
|
||||
read.o: read.c fmacros.h read.h sds.h
|
||||
sds.o: sds.c sds.h
|
||||
alloc.o: alloc.c fmacros.h alloc.h
|
||||
async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h win32.h async_private.h
|
||||
dict.o: dict.c fmacros.h alloc.h dict.h
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h
|
||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h
|
||||
read.o: read.c fmacros.h alloc.h read.h sds.h win32.h
|
||||
sds.o: sds.c sds.h sdsalloc.h alloc.h
|
||||
sockcompat.o: sockcompat.c sockcompat.h
|
||||
ssl.o: ssl.c hiredis.h
|
||||
test.o: test.c fmacros.h hiredis.h read.h sds.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 alloc.h net.h sockcompat.h win32.h
|
||||
|
||||
$(DYLIBNAME): $(OBJ)
|
||||
$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
|
||||
@ -112,7 +127,7 @@ $(STLIBNAME): $(OBJ)
|
||||
$(STLIB_MAKE_CMD) $(STLIBNAME) $(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)
|
||||
$(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)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||
|
||||
|
||||
ifndef AE_DIR
|
||||
hiredis-example-ae:
|
||||
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
||||
@ -180,13 +196,19 @@ endif
|
||||
hiredis-example: examples/example.c $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-push: examples/example-push.c $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
examples: $(EXAMPLES)
|
||||
|
||||
TEST_LIBS = $(STLIBNAME)
|
||||
ifeq ($(USE_SSL),1)
|
||||
TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread
|
||||
TEST_LIBS += $(SSL_STLIBNAME)
|
||||
TEST_LDFLAGS = $(SSL_LDFLAGS) -lssl -lcrypto -lpthread
|
||||
endif
|
||||
|
||||
hiredis-test: test.o $(TEST_LIBS)
|
||||
$(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS)
|
||||
|
||||
hiredis-%: %.o $(STLIBNAME)
|
||||
$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
|
||||
@ -221,7 +243,7 @@ $(PKGCONFNAME): hiredis.h
|
||||
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||
|
||||
$(SSL_PKGCONFNAME): hiredis.h
|
||||
$(SSL_PKGCONFNAME): hiredis_ssl.h
|
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@echo prefix=$(PREFIX) > $@
|
||||
@echo exec_prefix=\$${prefix} >> $@
|
||||
@ -237,7 +259,7 @@ $(SSL_PKGCONFNAME): hiredis.h
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||
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) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
||||
@ -245,6 +267,19 @@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||
mkdir -p $(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:
|
||||
@echo ""
|
||||
@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)
|
||||
|
||||
**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
|
||||
|
||||
@ -24,12 +24,31 @@ The library comes with multiple APIs. There is the
|
||||
|
||||
## 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 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).
|
||||
|
||||
_Note: As described below, a few member names have been changed but most applications should be able to upgrade with minor code changes and recompiling._
|
||||
|
||||
## IMPORTANT: Breaking changes from `0.14.1` -> `1.0.0`
|
||||
|
||||
* `redisContext` has two additional members (`free_privdata`, and `privctx`).
|
||||
* `redisOptions.timeout` has been renamed to `redisOptions.connect_timeout`, and we've added `redisOptions.command_timeout`.
|
||||
* `redisReplyObjectFunctions.createArray` now takes `size_t` instead of `int` for its length parameter.
|
||||
|
||||
## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x
|
||||
|
||||
Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
|
||||
protocol errors. This is consistent with the RESP specification. On 32-bit
|
||||
platforms, the upper bound is lowered to `SIZE_MAX`.
|
||||
|
||||
Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well. If it was used to
|
||||
compare to other values, casting might be necessary or can be removed, if
|
||||
casting was applied before.
|
||||
|
||||
## Upgrading from `<0.9.0`
|
||||
|
||||
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
|
||||
@ -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
|
||||
was received:
|
||||
|
||||
### RESP2
|
||||
|
||||
* **`REDIS_REPLY_STATUS`**:
|
||||
* The command replied with a status reply. The status string can be accessed using `reply->str`.
|
||||
The length of this string can be accessed using `reply->len`.
|
||||
@ -134,16 +155,51 @@ was received:
|
||||
and can be accessed via `reply->element[..index..]`.
|
||||
Redis may reply with nested arrays but this is fully supported.
|
||||
|
||||
### RESP3
|
||||
|
||||
Hiredis also supports every new `RESP3` data type which are as follows. For more information about the protocol see the `RESP3` [specification.](https://github.com/antirez/RESP3/blob/master/spec.md)
|
||||
|
||||
* **`REDIS_REPLY_DOUBLE`**:
|
||||
* The command replied with a double-precision floating point number.
|
||||
The value is stored as a string in the `str` member, and can be converted with `strtod` or similar.
|
||||
|
||||
* **`REDIS_REPLY_BOOL`**:
|
||||
* A boolean true/false reply.
|
||||
The value is stored in the `integer` member and will be either `0` or `1`.
|
||||
|
||||
* **`REDIS_REPLY_MAP`**:
|
||||
* An array with the added invariant that there will always be an even number of elements.
|
||||
The MAP is functionally 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.
|
||||
Note that this function will take care of freeing sub-reply objects
|
||||
contained in arrays and nested arrays, so there is no need for the user to
|
||||
free the sub replies (it is actually harmful and will corrupt the memory).
|
||||
|
||||
**Important:** the current version of hiredis (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
|
||||
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
|
||||
keep an eye on the changelog when upgrading (see issue #39).
|
||||
returns. We may introduce a flag to make this configurable in future versions of the library.
|
||||
|
||||
### Cleaning up
|
||||
|
||||
@ -205,16 +261,16 @@ a single call to `read(2)`):
|
||||
redisReply *reply;
|
||||
redisAppendCommand(context,"SET foo bar");
|
||||
redisAppendCommand(context,"GET foo");
|
||||
redisGetReply(context,&reply); // reply for SET
|
||||
redisGetReply(context,(void *)&reply); // reply for SET
|
||||
freeReplyObject(reply);
|
||||
redisGetReply(context,&reply); // reply for GET
|
||||
redisGetReply(context,(void *)&reply); // reply for GET
|
||||
freeReplyObject(reply);
|
||||
```
|
||||
This API can also be used to implement a blocking subscriber:
|
||||
```c
|
||||
reply = redisCommand(context,"SUBSCRIBE foo");
|
||||
freeReplyObject(reply);
|
||||
while(redisGetReply(context,&reply) == REDIS_OK) {
|
||||
while(redisGetReply(context,(void *)&reply) == REDIS_OK) {
|
||||
// consume message
|
||||
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
|
||||
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
|
||||
|
||||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
|
||||
Jan-Erik Rediger (janerik at fnordig dot com)
|
||||
Salvatore Sanfilippo (antirez at gmail),\
|
||||
Pieter Noordhuis (pcnoordhuis at gmail)\
|
||||
Michael Grunder (michael dot grunder at gmail)
|
||||
|
||||
_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;
|
||||
redisAeDelRead(privdata);
|
||||
redisAeDelWrite(privdata);
|
||||
free(e);
|
||||
hi_free(e);
|
||||
}
|
||||
|
||||
static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
|
||||
@ -108,7 +108,10 @@ static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
|
||||
return REDIS_ERR;
|
||||
|
||||
/* 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->loop = loop;
|
||||
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);
|
||||
|
||||
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
|
||||
if (source == NULL)
|
||||
return NULL;
|
||||
|
||||
source->ac = ac;
|
||||
source->poll_fd.fd = c->fd;
|
||||
source->poll_fd.events = 0;
|
||||
|
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;
|
||||
|
||||
iv_fd_unregister(&e->fd);
|
||||
free(e);
|
||||
hi_free(e);
|
||||
}
|
||||
|
||||
static int redisIvykisAttach(redisAsyncContext *ac) {
|
||||
@ -55,7 +55,10 @@ static int redisIvykisAttach(redisAsyncContext *ac) {
|
||||
return REDIS_ERR;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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;
|
||||
int reading, writing;
|
||||
ev_io rev, wev;
|
||||
ev_timer timer;
|
||||
} redisLibevEvents;
|
||||
|
||||
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) {
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
redisLibevDelRead(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) {
|
||||
@ -119,14 +148,16 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
|
||||
return REDIS_ERR;
|
||||
|
||||
/* 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;
|
||||
#if EV_MULTIPLICITY
|
||||
e->loop = loop;
|
||||
#else
|
||||
e->loop = NULL;
|
||||
#endif
|
||||
e->reading = e->writing = 0;
|
||||
e->rev.data = e;
|
||||
e->wev.data = e;
|
||||
|
||||
@ -136,6 +167,7 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
|
||||
ac->ev.addWrite = redisLibevAddWrite;
|
||||
ac->ev.delWrite = redisLibevDelWrite;
|
||||
ac->ev.cleanup = redisLibevCleanup;
|
||||
ac->ev.scheduleTimer = redisLibevSetTimeout;
|
||||
ac->ev.data = e;
|
||||
|
||||
/* 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;
|
||||
|
||||
static void redisLibeventDestroy(redisLibeventEvents *e) {
|
||||
free(e);
|
||||
hi_free(e);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
|
12
deps/hiredis/adapters/libuv.h
vendored
12
deps/hiredis/adapters/libuv.h
vendored
@ -73,7 +73,7 @@ static void redisLibuvDelWrite(void *privdata) {
|
||||
static void on_close(uv_handle_t* handle) {
|
||||
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.cleanup = redisLibuvCleanup;
|
||||
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p));
|
||||
|
||||
if (!p) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p));
|
||||
if (p == NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
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);
|
||||
CFRelease(redisRunLoop->socketRef);
|
||||
}
|
||||
free(redisRunLoop);
|
||||
hi_free(redisRunLoop);
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@ -80,8 +80,9 @@ static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLo
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
|
||||
|
||||
RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop));
|
||||
if( !redisRunLoop ) return REDIS_ERR;
|
||||
RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop));
|
||||
if (redisRunLoop == NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Setup redis stuff */
|
||||
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 */
|
225
deps/hiredis/async.c
vendored
225
deps/hiredis/async.c
vendored
@ -30,6 +30,7 @@
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "alloc.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef _MSC_VER
|
||||
@ -46,18 +47,24 @@
|
||||
|
||||
#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);
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
/* Functions managing dictionary of callbacks for pub/sub. */
|
||||
static unsigned int callbackHash(const void *key) {
|
||||
return dictGenHashFunction((const unsigned char *)key,
|
||||
sdslen((const sds)key));
|
||||
hi_sdslen((const hisds)key));
|
||||
}
|
||||
|
||||
static void *callbackValDup(void *privdata, const void *src) {
|
||||
((void) privdata);
|
||||
redisCallback *dup = malloc(sizeof(*dup));
|
||||
redisCallback *dup;
|
||||
|
||||
dup = hi_malloc(sizeof(*dup));
|
||||
if (dup == NULL)
|
||||
return NULL;
|
||||
|
||||
memcpy(dup,src,sizeof(*dup));
|
||||
return dup;
|
||||
}
|
||||
@ -66,20 +73,20 @@ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2
|
||||
int l1, l2;
|
||||
((void) privdata);
|
||||
|
||||
l1 = sdslen((const sds)key1);
|
||||
l2 = sdslen((const sds)key2);
|
||||
l1 = hi_sdslen((const hisds)key1);
|
||||
l2 = hi_sdslen((const hisds)key2);
|
||||
if (l1 != l2) return 0;
|
||||
return memcmp(key1,key2,l1) == 0;
|
||||
}
|
||||
|
||||
static void callbackKeyDestructor(void *privdata, void *key) {
|
||||
((void) privdata);
|
||||
sdsfree((sds)key);
|
||||
hi_sdsfree((hisds)key);
|
||||
}
|
||||
|
||||
static void callbackValDestructor(void *privdata, void *val) {
|
||||
((void) privdata);
|
||||
free(val);
|
||||
hi_free(val);
|
||||
}
|
||||
|
||||
static dictType callbackDict = {
|
||||
@ -93,10 +100,19 @@ static dictType callbackDict = {
|
||||
|
||||
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
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)
|
||||
return NULL;
|
||||
goto oom;
|
||||
|
||||
c = &(ac->c);
|
||||
|
||||
@ -108,6 +124,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
ac->err = 0;
|
||||
ac->errstr = NULL;
|
||||
ac->data = NULL;
|
||||
ac->dataCleanup = NULL;
|
||||
|
||||
ac->ev.data = NULL;
|
||||
ac->ev.addRead = NULL;
|
||||
@ -124,9 +141,14 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
ac->replies.tail = NULL;
|
||||
ac->sub.invalid.head = NULL;
|
||||
ac->sub.invalid.tail = NULL;
|
||||
ac->sub.channels = dictCreate(&callbackDict,NULL);
|
||||
ac->sub.patterns = dictCreate(&callbackDict,NULL);
|
||||
ac->sub.channels = channels;
|
||||
ac->sub.patterns = patterns;
|
||||
|
||||
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
|
||||
@ -145,16 +167,26 @@ redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
|
||||
redisContext *c;
|
||||
redisAsyncContext *ac;
|
||||
|
||||
/* Clear any erroneously set sync callback and flag that we don't want to
|
||||
* use freeReplyObject by default. */
|
||||
myOptions.push_cb = NULL;
|
||||
myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
|
||||
|
||||
myOptions.options |= REDIS_OPT_NONBLOCK;
|
||||
c = redisConnectWithOptions(&myOptions);
|
||||
if (c == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ac = redisAsyncInitialize(c);
|
||||
if (ac == NULL) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Set any configured async push handler */
|
||||
redisAsyncSetPushCallback(ac, myOptions.async_push_cb);
|
||||
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
@ -214,7 +246,7 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
|
||||
redisCallback *cb;
|
||||
|
||||
/* Copy callback from stack to heap */
|
||||
cb = malloc(sizeof(*cb));
|
||||
cb = hi_malloc(sizeof(*cb));
|
||||
if (cb == NULL)
|
||||
return REDIS_ERR_OOM;
|
||||
|
||||
@ -242,7 +274,7 @@ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target)
|
||||
/* Copy callback from heap to stack */
|
||||
if (target != NULL)
|
||||
memcpy(target,cb,sizeof(*cb));
|
||||
free(cb);
|
||||
hi_free(cb);
|
||||
return REDIS_OK;
|
||||
}
|
||||
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. */
|
||||
static void __redisAsyncFree(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
@ -272,18 +312,28 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
|
||||
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
|
||||
__redisRunCallback(ac,&cb,NULL);
|
||||
|
||||
/* Run subscription callbacks callbacks with NULL reply */
|
||||
it = dictGetIterator(ac->sub.channels);
|
||||
while ((de = dictNext(it)) != NULL)
|
||||
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
||||
dictReleaseIterator(it);
|
||||
dictRelease(ac->sub.channels);
|
||||
/* Run subscription callbacks with NULL reply */
|
||||
if (ac->sub.channels) {
|
||||
it = dictGetIterator(ac->sub.channels);
|
||||
if (it != NULL) {
|
||||
while ((de = dictNext(it)) != NULL)
|
||||
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
||||
dictReleaseIterator(it);
|
||||
}
|
||||
|
||||
it = dictGetIterator(ac->sub.patterns);
|
||||
while ((de = dictNext(it)) != NULL)
|
||||
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
||||
dictReleaseIterator(it);
|
||||
dictRelease(ac->sub.patterns);
|
||||
dictRelease(ac->sub.channels);
|
||||
}
|
||||
|
||||
if (ac->sub.patterns) {
|
||||
it = dictGetIterator(ac->sub.patterns);
|
||||
if (it != NULL) {
|
||||
while ((de = dictNext(it)) != NULL)
|
||||
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
|
||||
dictReleaseIterator(it);
|
||||
}
|
||||
|
||||
dictRelease(ac->sub.patterns);
|
||||
}
|
||||
|
||||
/* Signal event lib to clean up */
|
||||
_EL_CLEANUP(ac);
|
||||
@ -298,6 +348,10 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
|
||||
}
|
||||
}
|
||||
|
||||
if (ac->dataCleanup) {
|
||||
ac->dataCleanup(ac->data);
|
||||
}
|
||||
|
||||
/* Cleanup self */
|
||||
redisFree(c);
|
||||
}
|
||||
@ -364,11 +418,11 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
dictEntry *de;
|
||||
int pvariant;
|
||||
char *stype;
|
||||
sds sname;
|
||||
hisds sname;
|
||||
|
||||
/* Custom reply functions are not supported for pub/sub. This will fail
|
||||
* 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->element[0]->type == REDIS_REPLY_STRING);
|
||||
stype = reply->element[0]->str;
|
||||
@ -381,7 +435,10 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
|
||||
/* Locate the right callback */
|
||||
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);
|
||||
if (de != NULL) {
|
||||
cb = dictGetEntryVal(de);
|
||||
@ -409,12 +466,39 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
c->flags &= ~REDIS_SUBSCRIBED;
|
||||
}
|
||||
}
|
||||
sdsfree(sname);
|
||||
hi_sdsfree(sname);
|
||||
} else {
|
||||
/* Shift callback for invalid commands. */
|
||||
__redisShiftCallback(&ac->sub.invalid,dstcb);
|
||||
}
|
||||
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) {
|
||||
@ -427,7 +511,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
if (reply == NULL) {
|
||||
/* When the connection is being disconnected and there are
|
||||
* no more replies, this is the cue to really disconnect. */
|
||||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0
|
||||
if (c->flags & REDIS_DISCONNECTING && hi_sdslen(c->obuf) == 0
|
||||
&& ac->replies.head == NULL) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
@ -443,8 +527,18 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Even if the context is subscribed, pending regular callbacks will
|
||||
* get a reply before pub/sub messages arrive. */
|
||||
/* Send any non-subscribe related PUSH messages to our PUSH handler
|
||||
* while allowing subscribe related PUSH messages to pass through.
|
||||
* This allows existing code to be backward compatible and work in
|
||||
* either RESP2 or RESP3 mode. */
|
||||
if (redisIsSpontaneousPushReply(reply)) {
|
||||
__redisRunPushCallback(ac, reply);
|
||||
c->reader->fn->freeObject(reply);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Even if the context is subscribed, pending regular
|
||||
* callbacks will get a reply before pub/sub messages arrive. */
|
||||
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
|
||||
/*
|
||||
* A spontaneous reply in a not-subscribed context can be the error
|
||||
@ -497,20 +591,31 @@ void redisProcessCallbacks(redisAsyncContext *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
|
||||
* write event fires. When connecting was not successful, the connect callback
|
||||
* is called with a REDIS_ERR status and the context is free'd. */
|
||||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
||||
int completed = 0;
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
|
||||
/* Error! */
|
||||
redisCheckSocketError(c);
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
|
||||
__redisAsyncDisconnect(ac);
|
||||
__redisAsyncHandleConnectFailure(ac);
|
||||
return REDIS_ERR;
|
||||
} else if (completed == 1) {
|
||||
/* connected! */
|
||||
if (c->connection_type == REDIS_CONN_TCP &&
|
||||
redisSetTcpNoDelay(c) == REDIS_ERR) {
|
||||
__redisAsyncHandleConnectFailure(ac);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
@ -582,8 +687,6 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
c->funcs->async_write(ac);
|
||||
}
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
@ -641,7 +744,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
const char *cstr, *astr;
|
||||
size_t clen, alen;
|
||||
const char *p;
|
||||
sds sname;
|
||||
hisds sname;
|
||||
int ret;
|
||||
|
||||
/* 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. */
|
||||
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
||||
sname = sdsnewlen(astr,alen);
|
||||
sname = hi_sdsnewlen(astr,alen);
|
||||
if (sname == NULL)
|
||||
goto oom;
|
||||
|
||||
if (pvariant)
|
||||
cbdict = ac->sub.patterns;
|
||||
else
|
||||
@ -680,7 +786,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
|
||||
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) {
|
||||
/* 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);
|
||||
|
||||
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) {
|
||||
@ -722,7 +831,7 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
|
||||
return REDIS_ERR;
|
||||
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
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) {
|
||||
sds cmd;
|
||||
hisds cmd;
|
||||
int len;
|
||||
int status;
|
||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
||||
if (len < 0)
|
||||
return REDIS_ERR;
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
sdsfree(cmd);
|
||||
hi_sdsfree(cmd);
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -752,15 +861,27 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
return status;
|
||||
}
|
||||
|
||||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
|
||||
if (!ac->c.timeout) {
|
||||
ac->c.timeout = calloc(1, sizeof(tv));
|
||||
}
|
||||
|
||||
if (tv.tv_sec == ac->c.timeout->tv_sec &&
|
||||
tv.tv_usec == ac->c.timeout->tv_usec) {
|
||||
return;
|
||||
}
|
||||
|
||||
*ac->c.timeout = tv;
|
||||
redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) {
|
||||
redisAsyncPushFn *old = ac->push_cb;
|
||||
ac->push_cb = fn;
|
||||
return old;
|
||||
}
|
||||
|
||||
int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
|
||||
if (!ac->c.command_timeout) {
|
||||
ac->c.command_timeout = hi_calloc(1, sizeof(tv));
|
||||
if (ac->c.command_timeout == NULL) {
|
||||
__redisSetError(&ac->c, REDIS_ERR_OOM, "Out of memory");
|
||||
__redisAsyncCopyError(ac);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (tv.tv_sec != ac->c.command_timeout->tv_sec ||
|
||||
tv.tv_usec != ac->c.command_timeout->tv_usec)
|
||||
{
|
||||
*ac->c.command_timeout = tv;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
7
deps/hiredis/async.h
vendored
7
deps/hiredis/async.h
vendored
@ -70,6 +70,7 @@ typedef struct redisAsyncContext {
|
||||
|
||||
/* Not used by hiredis */
|
||||
void *data;
|
||||
void (*dataCleanup)(void *privdata);
|
||||
|
||||
/* Event library data and hooks */
|
||||
struct {
|
||||
@ -105,6 +106,9 @@ typedef struct redisAsyncContext {
|
||||
struct dict *channels;
|
||||
struct dict *patterns;
|
||||
} sub;
|
||||
|
||||
/* Any configured RESP3 PUSH handler */
|
||||
redisAsyncPushFn *push_cb;
|
||||
} redisAsyncContext;
|
||||
|
||||
/* Functions that proxy to hiredis */
|
||||
@ -117,7 +121,8 @@ redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *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 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 { \
|
||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||
ctx->ev.cleanup = NULL; \
|
||||
} while(0);
|
||||
} while(0)
|
||||
|
||||
static inline void refreshTimeout(redisAsyncContext *ctx) {
|
||||
if (ctx->c.timeout && ctx->ev.scheduleTimer &&
|
||||
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) {
|
||||
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
|
||||
// } else {
|
||||
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
|
||||
// if (ctx->c.timeout){
|
||||
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
|
||||
// ctx->c.timeout->tv_usec);
|
||||
// }
|
||||
#define REDIS_TIMER_ISSET(tvp) \
|
||||
(tvp && ((tvp)->tv_sec || (tvp)->tv_usec))
|
||||
|
||||
#define REDIS_EL_TIMER(ac, tvp) \
|
||||
if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \
|
||||
(ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \
|
||||
}
|
||||
|
||||
if (ctx->c.flags & REDIS_CONNECTED) {
|
||||
REDIS_EL_TIMER(ctx, ctx->c.command_timeout);
|
||||
} else {
|
||||
REDIS_EL_TIMER(ctx, ctx->c.connect_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
36
deps/hiredis/dict.c
vendored
36
deps/hiredis/dict.c
vendored
@ -34,6 +34,7 @@
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "alloc.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
@ -71,7 +72,10 @@ static void _dictReset(dict *ht) {
|
||||
|
||||
/* Create a new hash table */
|
||||
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);
|
||||
return ht;
|
||||
}
|
||||
@ -97,7 +101,9 @@ static int dictExpand(dict *ht, unsigned long size) {
|
||||
_dictInit(&n, ht->type, ht->privdata);
|
||||
n.size = realsize;
|
||||
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:
|
||||
* 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);
|
||||
free(ht->table);
|
||||
hi_free(ht->table);
|
||||
|
||||
/* Remap the new hashtable in the old */
|
||||
*ht = n;
|
||||
@ -142,7 +148,10 @@ static int dictAdd(dict *ht, void *key, void *val) {
|
||||
return DICT_ERR;
|
||||
|
||||
/* 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];
|
||||
ht->table[index] = entry;
|
||||
|
||||
@ -166,6 +175,9 @@ static int dictReplace(dict *ht, void *key, void *val) {
|
||||
return 1;
|
||||
/* It already exists, get the entry */
|
||||
entry = dictFind(ht, key);
|
||||
if (entry == NULL)
|
||||
return 0;
|
||||
|
||||
/* Free the old value and set the new one */
|
||||
/* Set the new value and free the old one. Note that it is important
|
||||
* to do that in this order, as the value may just be exactly the same
|
||||
@ -199,7 +211,7 @@ static int dictDelete(dict *ht, const void *key) {
|
||||
|
||||
dictFreeEntryKey(ht,de);
|
||||
dictFreeEntryVal(ht,de);
|
||||
free(de);
|
||||
hi_free(de);
|
||||
ht->used--;
|
||||
return DICT_OK;
|
||||
}
|
||||
@ -222,13 +234,13 @@ static int _dictClear(dict *ht) {
|
||||
nextHe = he->next;
|
||||
dictFreeEntryKey(ht, he);
|
||||
dictFreeEntryVal(ht, he);
|
||||
free(he);
|
||||
hi_free(he);
|
||||
ht->used--;
|
||||
he = nextHe;
|
||||
}
|
||||
}
|
||||
/* Free the table and the allocated cache structure */
|
||||
free(ht->table);
|
||||
hi_free(ht->table);
|
||||
/* Re-initialize the table */
|
||||
_dictReset(ht);
|
||||
return DICT_OK; /* never fails */
|
||||
@ -237,7 +249,7 @@ static int _dictClear(dict *ht) {
|
||||
/* Clear & Release the hash table */
|
||||
static void dictRelease(dict *ht) {
|
||||
_dictClear(ht);
|
||||
free(ht);
|
||||
hi_free(ht);
|
||||
}
|
||||
|
||||
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) {
|
||||
dictIterator *iter = malloc(sizeof(*iter));
|
||||
dictIterator *iter = hi_malloc(sizeof(*iter));
|
||||
if (iter == NULL)
|
||||
return NULL;
|
||||
|
||||
iter->ht = ht;
|
||||
iter->index = -1;
|
||||
@ -286,7 +300,7 @@ static dictEntry *dictNext(dictIterator *iter) {
|
||||
}
|
||||
|
||||
static void dictReleaseIterator(dictIterator *iter) {
|
||||
free(iter);
|
||||
hi_free(iter);
|
||||
}
|
||||
|
||||
/* ------------------------- private functions ------------------------------ */
|
||||
@ -294,7 +308,7 @@ static void dictReleaseIterator(dictIterator *iter) {
|
||||
/* Expand the hash table if needed */
|
||||
static int _dictExpandIfNeeded(dict *ht) {
|
||||
/* If the hash table is empty expand it to the initial size,
|
||||
* if the table is "full" dobule its size. */
|
||||
* if the table is "full" double its size. */
|
||||
if (ht->size == 0)
|
||||
return dictExpand(ht, DICT_HT_INITIAL_SIZE);
|
||||
if (ht->used == ht->size)
|
||||
|
3
deps/hiredis/examples/CMakeLists.txt
vendored
3
deps/hiredis/examples/CMakeLists.txt
vendored
@ -44,3 +44,6 @@ ENDIF()
|
||||
|
||||
ADD_EXECUTABLE(example example.c)
|
||||
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) {
|
||||
#ifndef _WIN32
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
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) {
|
||||
#ifndef _WIN32
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
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) {
|
||||
#ifndef _WIN32
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
struct event_base *base = event_base_new();
|
||||
if (argc < 5) {
|
||||
fprintf(stderr,
|
||||
@ -52,13 +55,25 @@ int main (int argc, char **argv) {
|
||||
const char *certKey = argv[5];
|
||||
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);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
|
||||
if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK) {
|
||||
printf("SSL Error!\n");
|
||||
exit(1);
|
||||
}
|
||||
@ -69,5 +84,7 @@ int main (int argc, char **argv) {
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
event_base_dispatch(base);
|
||||
|
||||
redisFreeSSLContext(ssl);
|
||||
return 0;
|
||||
}
|
||||
|
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) {
|
||||
#ifndef _WIN32
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
struct event_base *base = event_base_new();
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
|
||||
struct timeval tv = {0};
|
||||
tv.tv_sec = 1;
|
||||
options.timeout = &tv;
|
||||
options.connect_timeout = &tv;
|
||||
|
||||
|
||||
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) {
|
||||
#ifndef _WIN32
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
uv_loop_t* loop = uv_default_loop();
|
||||
|
||||
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_ssl.h>
|
||||
#include <win32.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned int j;
|
||||
redisSSLContext *ssl;
|
||||
redisSSLContextError ssl_error;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
if (argc < 4) {
|
||||
@ -19,10 +22,18 @@ int main(int argc, char **argv) {
|
||||
const char *key = argv[4];
|
||||
const char *ca = argc > 4 ? argv[5] : NULL;
|
||||
|
||||
redisInitOpenSSL();
|
||||
ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
|
||||
if (!ssl) {
|
||||
printf("SSL Context error: %s\n",
|
||||
redisSSLContextGetError(ssl_error));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
struct timeval tv = { 1, 500000 }; // 1.5 seconds
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, hostname, port);
|
||||
options.timeout = &tv;
|
||||
options.connect_timeout = &tv;
|
||||
c = redisConnectWithOptions(&options);
|
||||
|
||||
if (c == NULL || c->err) {
|
||||
@ -35,7 +46,7 @@ int main(int argc, char **argv) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) {
|
||||
if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
|
||||
printf("Couldn't initialize SSL!\n");
|
||||
printf("Error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
@ -93,5 +104,7 @@ int main(int argc, char **argv) {
|
||||
/* Disconnects and frees the context */
|
||||
redisFree(c);
|
||||
|
||||
redisFreeSSLContext(ssl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
2
deps/hiredis/examples/example.c
vendored
2
deps/hiredis/examples/example.c
vendored
@ -1,8 +1,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <win32.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
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)
|
||||
|
316
deps/hiredis/hiredis.c
vendored
316
deps/hiredis/hiredis.c
vendored
@ -44,8 +44,11 @@
|
||||
#include "async.h"
|
||||
#include "win32.h"
|
||||
|
||||
extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout);
|
||||
extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
|
||||
|
||||
static redisContextFuncs redisContextDefaultFuncs = {
|
||||
.free_privdata = NULL,
|
||||
.free_privctx = NULL,
|
||||
.async_read = redisAsyncRead,
|
||||
.async_write = redisAsyncWrite,
|
||||
.read = redisNetRead,
|
||||
@ -74,7 +77,7 @@ static redisReplyObjectFunctions defaultFunctions = {
|
||||
|
||||
/* Create a reply object */
|
||||
static redisReply *createReplyObject(int type) {
|
||||
redisReply *r = calloc(1,sizeof(*r));
|
||||
redisReply *r = hi_calloc(1,sizeof(*r));
|
||||
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
@ -97,20 +100,22 @@ void freeReplyObject(void *reply) {
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
case REDIS_REPLY_PUSH:
|
||||
if (r->element != NULL) {
|
||||
for (j = 0; j < r->elements; j++)
|
||||
freeReplyObject(r->element[j]);
|
||||
free(r->element);
|
||||
hi_free(r->element);
|
||||
}
|
||||
break;
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
free(r->str);
|
||||
case REDIS_REPLY_VERB:
|
||||
hi_free(r->str);
|
||||
break;
|
||||
}
|
||||
free(r);
|
||||
hi_free(r);
|
||||
}
|
||||
|
||||
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 */
|
||||
if (task->type == REDIS_REPLY_VERB) {
|
||||
buf = malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
|
||||
if (buf == NULL) goto oom;
|
||||
|
||||
memcpy(r->vtype,str,3);
|
||||
r->vtype[3] = '\0';
|
||||
memcpy(buf,str+4,len-4);
|
||||
buf[len-4] = '\0';
|
||||
r->len = len-4;
|
||||
r->len = len - 4;
|
||||
} else {
|
||||
buf = malloc(len+1);
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
buf = hi_malloc(len+1);
|
||||
if (buf == NULL) goto oom;
|
||||
|
||||
memcpy(buf,str,len);
|
||||
buf[len] = '\0';
|
||||
r->len = len;
|
||||
@ -154,10 +155,15 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
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;
|
||||
}
|
||||
return r;
|
||||
|
||||
oom:
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
||||
@ -168,7 +174,7 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
||||
return NULL;
|
||||
|
||||
if (elements > 0) {
|
||||
r->element = calloc(elements,sizeof(redisReply*));
|
||||
r->element = hi_calloc(elements,sizeof(redisReply*));
|
||||
if (r->element == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
@ -181,7 +187,8 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
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;
|
||||
}
|
||||
return r;
|
||||
@ -200,7 +207,8 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
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;
|
||||
}
|
||||
return r;
|
||||
@ -214,7 +222,7 @@ static void *createDoubleObject(const redisReadTask *task, double value, char *s
|
||||
return NULL;
|
||||
|
||||
r->dval = value;
|
||||
r->str = malloc(len+1);
|
||||
r->str = hi_malloc(len+1);
|
||||
if (r->str == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
@ -249,7 +257,8 @@ static void *createNilObject(const redisReadTask *task) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
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;
|
||||
}
|
||||
return r;
|
||||
@ -297,7 +306,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
const char *c = format;
|
||||
char *cmd = NULL; /* final command */
|
||||
int pos; /* position in final command */
|
||||
sds curarg, newarg; /* current argument */
|
||||
hisds curarg, newarg; /* current argument */
|
||||
int touched = 0; /* was the current argument touched? */
|
||||
char **curargv = NULL, **newargv = NULL;
|
||||
int argc = 0;
|
||||
@ -310,7 +319,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
return -1;
|
||||
|
||||
/* Build the command string accordingly to protocol */
|
||||
curarg = sdsempty();
|
||||
curarg = hi_sdsempty();
|
||||
if (curarg == NULL)
|
||||
return -1;
|
||||
|
||||
@ -318,19 +327,19 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
if (*c != '%' || c[1] == '\0') {
|
||||
if (*c == ' ') {
|
||||
if (touched) {
|
||||
newargv = realloc(curargv,sizeof(char*)*(argc+1));
|
||||
newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
|
||||
if (newargv == NULL) goto memory_err;
|
||||
curargv = newargv;
|
||||
curargv[argc++] = curarg;
|
||||
totlen += bulklen(sdslen(curarg));
|
||||
totlen += bulklen(hi_sdslen(curarg));
|
||||
|
||||
/* curarg is put in argv so it can be overwritten. */
|
||||
curarg = sdsempty();
|
||||
curarg = hi_sdsempty();
|
||||
if (curarg == NULL) goto memory_err;
|
||||
touched = 0;
|
||||
}
|
||||
} else {
|
||||
newarg = sdscatlen(curarg,c,1);
|
||||
newarg = hi_sdscatlen(curarg,c,1);
|
||||
if (newarg == NULL) goto memory_err;
|
||||
curarg = newarg;
|
||||
touched = 1;
|
||||
@ -347,16 +356,16 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
arg = va_arg(ap,char*);
|
||||
size = strlen(arg);
|
||||
if (size > 0)
|
||||
newarg = sdscatlen(curarg,arg,size);
|
||||
newarg = hi_sdscatlen(curarg,arg,size);
|
||||
break;
|
||||
case 'b':
|
||||
arg = va_arg(ap,char*);
|
||||
size = va_arg(ap,size_t);
|
||||
if (size > 0)
|
||||
newarg = sdscatlen(curarg,arg,size);
|
||||
newarg = hi_sdscatlen(curarg,arg,size);
|
||||
break;
|
||||
case '%':
|
||||
newarg = sdscat(curarg,"%");
|
||||
newarg = hi_sdscat(curarg,"%");
|
||||
break;
|
||||
default:
|
||||
/* Try to detect printf format */
|
||||
@ -444,7 +453,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
if (_l < sizeof(_format)-2) {
|
||||
memcpy(_format,c,_l);
|
||||
_format[_l] = '\0';
|
||||
newarg = sdscatvprintf(curarg,_format,_cpy);
|
||||
newarg = hi_sdscatvprintf(curarg,_format,_cpy);
|
||||
|
||||
/* Update current position (note: outer blocks
|
||||
* 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 */
|
||||
if (touched) {
|
||||
newargv = realloc(curargv,sizeof(char*)*(argc+1));
|
||||
newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
|
||||
if (newargv == NULL) goto memory_err;
|
||||
curargv = newargv;
|
||||
curargv[argc++] = curarg;
|
||||
totlen += bulklen(sdslen(curarg));
|
||||
totlen += bulklen(hi_sdslen(curarg));
|
||||
} else {
|
||||
sdsfree(curarg);
|
||||
hi_sdsfree(curarg);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
/* Build the command at protocol level */
|
||||
cmd = malloc(totlen+1);
|
||||
cmd = hi_malloc(totlen+1);
|
||||
if (cmd == NULL) goto memory_err;
|
||||
|
||||
pos = sprintf(cmd,"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j]));
|
||||
memcpy(cmd+pos,curargv[j],sdslen(curargv[j]));
|
||||
pos += sdslen(curargv[j]);
|
||||
sdsfree(curargv[j]);
|
||||
pos += sprintf(cmd+pos,"$%zu\r\n",hi_sdslen(curargv[j]));
|
||||
memcpy(cmd+pos,curargv[j],hi_sdslen(curargv[j]));
|
||||
pos += hi_sdslen(curargv[j]);
|
||||
hi_sdsfree(curargv[j]);
|
||||
cmd[pos++] = '\r';
|
||||
cmd[pos++] = '\n';
|
||||
}
|
||||
assert(pos == totlen);
|
||||
cmd[pos] = '\0';
|
||||
|
||||
free(curargv);
|
||||
hi_free(curargv);
|
||||
*target = cmd;
|
||||
return totlen;
|
||||
|
||||
@ -513,12 +522,12 @@ memory_err:
|
||||
cleanup:
|
||||
if (curargv) {
|
||||
while(argc--)
|
||||
sdsfree(curargv[argc]);
|
||||
free(curargv);
|
||||
hi_sdsfree(curargv[argc]);
|
||||
hi_free(curargv);
|
||||
}
|
||||
|
||||
sdsfree(curarg);
|
||||
free(cmd);
|
||||
hi_sdsfree(curarg);
|
||||
hi_free(cmd);
|
||||
|
||||
return error_type;
|
||||
}
|
||||
@ -550,16 +559,16 @@ int redisFormatCommand(char **target, const char *format, ...) {
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Format a command according to the Redis protocol using an sds string and
|
||||
* sdscatfmt for the processing of arguments. This function takes the
|
||||
/* Format a command according to the Redis protocol using an hisds string and
|
||||
* hi_sdscatfmt for the processing of arguments. This function takes the
|
||||
* number of arguments, an array with arguments and an array with their
|
||||
* lengths. If the latter is set to NULL, strlen will be used to compute the
|
||||
* argument lengths.
|
||||
*/
|
||||
int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
|
||||
int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv,
|
||||
const size_t *argvlen)
|
||||
{
|
||||
sds cmd;
|
||||
hisds cmd, aux;
|
||||
unsigned long long totlen;
|
||||
int j;
|
||||
size_t len;
|
||||
@ -576,32 +585,36 @@ int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
|
||||
}
|
||||
|
||||
/* Use an SDS string for command construction */
|
||||
cmd = sdsempty();
|
||||
cmd = hi_sdsempty();
|
||||
if (cmd == NULL)
|
||||
return -1;
|
||||
|
||||
/* We already know how much storage we need */
|
||||
cmd = sdsMakeRoomFor(cmd, totlen);
|
||||
if (cmd == NULL)
|
||||
aux = hi_sdsMakeRoomFor(cmd, totlen);
|
||||
if (aux == NULL) {
|
||||
hi_sdsfree(cmd);
|
||||
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;
|
||||
return totlen;
|
||||
}
|
||||
|
||||
void redisFreeSdsCommand(sds cmd) {
|
||||
sdsfree(cmd);
|
||||
void redisFreeSdsCommand(hisds cmd) {
|
||||
hi_sdsfree(cmd);
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
cmd = malloc(totlen+1);
|
||||
cmd = hi_malloc(totlen+1);
|
||||
if (cmd == NULL)
|
||||
return -1;
|
||||
|
||||
@ -648,7 +661,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
|
||||
}
|
||||
|
||||
void redisFreeCommand(char *cmd) {
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
}
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const char *str) {
|
||||
@ -671,15 +684,21 @@ redisReader *redisReaderCreate(void) {
|
||||
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;
|
||||
|
||||
c = calloc(1, sizeof(*c));
|
||||
c = hi_calloc(1, sizeof(*c));
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->funcs = &redisContextDefaultFuncs;
|
||||
c->obuf = sdsempty();
|
||||
|
||||
c->obuf = hi_sdsempty();
|
||||
c->reader = redisReaderCreate();
|
||||
c->fd = REDIS_INVALID_FD;
|
||||
|
||||
@ -687,7 +706,7 @@ static redisContext *redisContextInit(const redisOptions *options) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
(void)options; /* options are used in other functions */
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@ -696,18 +715,23 @@ void redisFree(redisContext *c) {
|
||||
return;
|
||||
redisNetClose(c);
|
||||
|
||||
sdsfree(c->obuf);
|
||||
hi_sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
free(c->tcp.host);
|
||||
free(c->tcp.source_addr);
|
||||
free(c->unix_sock.path);
|
||||
free(c->timeout);
|
||||
free(c->saddr);
|
||||
if (c->funcs->free_privdata) {
|
||||
c->funcs->free_privdata(c->privdata);
|
||||
}
|
||||
hi_free(c->tcp.host);
|
||||
hi_free(c->tcp.source_addr);
|
||||
hi_free(c->unix_sock.path);
|
||||
hi_free(c->connect_timeout);
|
||||
hi_free(c->command_timeout);
|
||||
hi_free(c->saddr);
|
||||
|
||||
if (c->privdata && c->free_privdata)
|
||||
c->free_privdata(c->privdata);
|
||||
|
||||
if (c->funcs->free_privctx)
|
||||
c->funcs->free_privctx(c->privctx);
|
||||
|
||||
memset(c, 0xff, sizeof(*c));
|
||||
free(c);
|
||||
hi_free(c);
|
||||
}
|
||||
|
||||
redisFD redisFreeKeepFd(redisContext *c) {
|
||||
@ -721,35 +745,46 @@ int redisReconnect(redisContext *c) {
|
||||
c->err = 0;
|
||||
memset(c->errstr, '\0', strlen(c->errstr));
|
||||
|
||||
if (c->privdata && c->funcs->free_privdata) {
|
||||
c->funcs->free_privdata(c->privdata);
|
||||
c->privdata = NULL;
|
||||
if (c->privctx && c->funcs->free_privctx) {
|
||||
c->funcs->free_privctx(c->privctx);
|
||||
c->privctx = NULL;
|
||||
}
|
||||
|
||||
redisNetClose(c);
|
||||
|
||||
sdsfree(c->obuf);
|
||||
hi_sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
|
||||
c->obuf = sdsempty();
|
||||
c->obuf = hi_sdsempty();
|
||||
c->reader = redisReaderCreate();
|
||||
|
||||
if (c->obuf == NULL || c->reader == NULL) {
|
||||
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
int ret = REDIS_ERR;
|
||||
if (c->connection_type == REDIS_CONN_TCP) {
|
||||
return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
|
||||
c->timeout, c->tcp.source_addr);
|
||||
ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
|
||||
c->connect_timeout, c->tcp.source_addr);
|
||||
} 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 {
|
||||
/* Something bad happened here and shouldn't have. There isn't
|
||||
enough information in the context to reconnect. */
|
||||
__redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
|
||||
ret = REDIS_ERR;
|
||||
}
|
||||
|
||||
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 *c = redisContextInit(options);
|
||||
redisContext *c = redisContextInit();
|
||||
if (c == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
@ -760,16 +795,32 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
|
||||
c->flags |= REDIS_REUSEADDR;
|
||||
}
|
||||
if (options->options & REDIS_OPT_NOAUTOFREE) {
|
||||
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) {
|
||||
redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
|
||||
options->endpoint.tcp.port, options->timeout,
|
||||
options->endpoint.tcp.port, options->connect_timeout,
|
||||
options->endpoint.tcp.source_addr);
|
||||
} else if (options->type == REDIS_CONN_UNIX) {
|
||||
redisContextConnectUnix(c, options->endpoint.unix_socket,
|
||||
options->timeout);
|
||||
options->connect_timeout);
|
||||
} else if (options->type == REDIS_CONN_USERFD) {
|
||||
c->fd = options->endpoint.fd;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
@ -777,9 +828,11 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
|
||||
// Unknown type - FIXME - FREE
|
||||
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;
|
||||
}
|
||||
|
||||
@ -795,7 +848,7 @@ redisContext *redisConnect(const char *ip, int port) {
|
||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.timeout = &tv;
|
||||
options.connect_timeout = &tv;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
@ -833,7 +886,7 @@ redisContext *redisConnectUnix(const char *path) {
|
||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
options.timeout = &tv;
|
||||
options.connect_timeout = &tv;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
@ -865,6 +918,13 @@ int redisEnableKeepAlive(redisContext *c) {
|
||||
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
|
||||
* 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)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (sdslen(c->obuf) > 0) {
|
||||
int nwritten = c->funcs->write(c);
|
||||
if (hi_sdslen(c->obuf) > 0) {
|
||||
ssize_t nwritten = c->funcs->write(c);
|
||||
if (nwritten < 0) {
|
||||
return REDIS_ERR;
|
||||
} else if (nwritten > 0) {
|
||||
if (nwritten == (signed)sdslen(c->obuf)) {
|
||||
sdsfree(c->obuf);
|
||||
c->obuf = sdsempty();
|
||||
if (nwritten == (ssize_t)hi_sdslen(c->obuf)) {
|
||||
hi_sdsfree(c->obuf);
|
||||
c->obuf = hi_sdsempty();
|
||||
if (c->obuf == NULL)
|
||||
goto oom;
|
||||
} 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;
|
||||
|
||||
oom:
|
||||
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
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 wdone = 0;
|
||||
void *aux = NULL;
|
||||
@ -953,13 +1031,23 @@ int redisGetReply(redisContext *c, void **reply) {
|
||||
do {
|
||||
if (redisBufferRead(c) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
if (redisGetReplyFromReader(c,&aux) == 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)
|
||||
return REDIS_ERR;
|
||||
} while (redisHandledPushReply(c, aux));
|
||||
} while (aux == NULL);
|
||||
}
|
||||
|
||||
/* Set reply object */
|
||||
if (reply != NULL) *reply = aux;
|
||||
/* Set reply or free it if we were passed NULL */
|
||||
if (reply != NULL) {
|
||||
*reply = aux;
|
||||
} else {
|
||||
freeReplyObject(aux);
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@ -971,9 +1059,9 @@ int redisGetReply(redisContext *c, void **reply) {
|
||||
* the reply (or replies in pub/sub).
|
||||
*/
|
||||
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) {
|
||||
__redisSetError(c,REDIS_ERR_OOM,"Out of memory");
|
||||
return REDIS_ERR;
|
||||
@ -1006,11 +1094,11 @@ int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
|
||||
}
|
||||
|
||||
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
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) {
|
||||
sds cmd;
|
||||
hisds cmd;
|
||||
int len;
|
||||
|
||||
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) {
|
||||
sdsfree(cmd);
|
||||
hi_sdsfree(cmd);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
sdsfree(cmd);
|
||||
hi_sdsfree(cmd);
|
||||
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 */
|
||||
#else
|
||||
struct timeval; /* forward declaration */
|
||||
typedef long long ssize_t;
|
||||
#endif
|
||||
#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_MINOR 14
|
||||
#define HIREDIS_MAJOR 1
|
||||
#define HIREDIS_MINOR 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
|
||||
* least significant bit of the flags field in redisContext. */
|
||||
@ -90,6 +92,15 @@ struct timeval; /* forward declaration */
|
||||
* SO_REUSEADDR is being used. */
|
||||
#define REDIS_CONNECT_RETRIES 10
|
||||
|
||||
/* Forward declarations for structs defined elsewhere */
|
||||
struct redisAsyncContext;
|
||||
struct redisContext;
|
||||
|
||||
/* RESP3 push helpers and callback prototypes */
|
||||
#define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH)
|
||||
typedef void (redisPushFn)(void *, void *);
|
||||
typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -101,7 +112,7 @@ typedef struct redisReply {
|
||||
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
|
||||
size_t len; /* Length of string */
|
||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||
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
|
||||
terminated 3 character content type, such as "txt". */
|
||||
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 redisFormatCommand(char **target, const char *format, ...);
|
||||
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 redisFreeSdsCommand(sds cmd);
|
||||
void redisFreeSdsCommand(hisds cmd);
|
||||
|
||||
enum redisConnectionType {
|
||||
REDIS_CONN_TCP,
|
||||
@ -138,6 +149,9 @@ struct redisSsl;
|
||||
*/
|
||||
#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
|
||||
* representing an invalid descriptor. In Windows it is a SOCKET
|
||||
* (32- or 64-bit unsigned integer depending on the architecture), where
|
||||
@ -162,8 +176,11 @@ typedef struct {
|
||||
int type;
|
||||
/* bit field of REDIS_OPT_xxx */
|
||||
int options;
|
||||
/* timeout value. if NULL, no timeout is used */
|
||||
const struct timeval *timeout;
|
||||
/* timeout value for connect operation. If NULL, no timeout is used */
|
||||
const struct timeval *connect_timeout;
|
||||
/* timeout value for commands. If NULL, no timeout is used. This can be
|
||||
* updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */
|
||||
const struct timeval *command_timeout;
|
||||
union {
|
||||
/** use this field for tcp/ip connections */
|
||||
struct {
|
||||
@ -178,6 +195,14 @@ typedef struct {
|
||||
* file descriptor */
|
||||
redisFD fd;
|
||||
} endpoint;
|
||||
|
||||
/* Optional user defined data/destructor */
|
||||
void *privdata;
|
||||
void (*free_privdata)(void *);
|
||||
|
||||
/* A user defined PUSH message callback */
|
||||
redisPushFn *push_cb;
|
||||
redisAsyncPushFn *async_push_cb;
|
||||
} redisOptions;
|
||||
|
||||
/**
|
||||
@ -192,15 +217,16 @@ typedef struct {
|
||||
(opts)->type = REDIS_CONN_UNIX; \
|
||||
(opts)->endpoint.unix_socket = path;
|
||||
|
||||
struct redisAsyncContext;
|
||||
struct redisContext;
|
||||
#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) \
|
||||
(opts)->privdata = data; \
|
||||
(opts)->free_privdata = dtor; \
|
||||
|
||||
typedef struct redisContextFuncs {
|
||||
void (*free_privdata)(void *);
|
||||
void (*free_privctx)(void *);
|
||||
void (*async_read)(struct redisAsyncContext *);
|
||||
void (*async_write)(struct redisAsyncContext *);
|
||||
int (*read)(struct redisContext *, char *, size_t);
|
||||
int (*write)(struct redisContext *);
|
||||
ssize_t (*read)(struct redisContext *, char *, size_t);
|
||||
ssize_t (*write)(struct redisContext *);
|
||||
} redisContextFuncs;
|
||||
|
||||
/* Context for a connection to Redis */
|
||||
@ -215,7 +241,8 @@ typedef struct redisContext {
|
||||
redisReader *reader; /* Protocol reader */
|
||||
|
||||
enum redisConnectionType connection_type;
|
||||
struct timeval *timeout;
|
||||
struct timeval *connect_timeout;
|
||||
struct timeval *command_timeout;
|
||||
|
||||
struct {
|
||||
char *host;
|
||||
@ -231,8 +258,17 @@ typedef struct redisContext {
|
||||
struct sockadr *saddr;
|
||||
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 (*free_privdata)(void *);
|
||||
|
||||
/* Internal context pointer presently used by hiredis to manage
|
||||
* SSL connections. */
|
||||
void *privctx;
|
||||
|
||||
/* An optional RESP3 PUSH handler */
|
||||
redisPushFn *push_cb;
|
||||
} redisContext;
|
||||
|
||||
redisContext *redisConnectWithOptions(const redisOptions *options);
|
||||
@ -259,6 +295,7 @@ redisContext *redisConnectFd(redisFD fd);
|
||||
*/
|
||||
int redisReconnect(redisContext *c);
|
||||
|
||||
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);
|
||||
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||
int redisEnableKeepAlive(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@
|
||||
install_libdir=@CMAKE_INSTALL_LIBDIR@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
libdir=${exec_prefix}/${install_libdir}
|
||||
includedir=${prefix}/include
|
||||
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
|
||||
#define __HIREDIS_SSL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This is the underlying struct for SSL in ssl.h, which is not included to
|
||||
* keep build dependencies short here.
|
||||
*/
|
||||
struct ssl_st;
|
||||
|
||||
/**
|
||||
* Secure the connection using SSL. This should be done before any command is
|
||||
* executed on the connection.
|
||||
/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
|
||||
* calling OpenSSL.
|
||||
*/
|
||||
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
|
||||
const char *keypath, const char *servername);
|
||||
typedef struct redisSSLContext redisSSLContext;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#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) {
|
||||
int nread = recv(c->fd, buf, bufcap, 0);
|
||||
ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
ssize_t nread = recv(c->fd, buf, bufcap, 0);
|
||||
if (nread == -1) {
|
||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
@ -79,8 +79,8 @@ int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
}
|
||||
}
|
||||
|
||||
int redisNetWrite(redisContext *c) {
|
||||
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
|
||||
ssize_t redisNetWrite(redisContext *c) {
|
||||
ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0);
|
||||
if (nwritten < 0) {
|
||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
@ -203,7 +203,7 @@ int redisKeepAlive(redisContext *c, int interval) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int redisSetTcpNoDelay(redisContext *c) {
|
||||
int redisSetTcpNoDelay(redisContext *c) {
|
||||
int yes = 1;
|
||||
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
||||
@ -217,7 +217,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
|
||||
|
||||
static int redisContextTimeoutMsec(redisContext *c, long *result)
|
||||
{
|
||||
const struct timeval *timeout = c->timeout;
|
||||
const struct timeval *timeout = c->connect_timeout;
|
||||
long msec = -1;
|
||||
|
||||
/* Only use timeout when not NULL. */
|
||||
@ -316,11 +316,7 @@ int redisCheckSocketError(redisContext *c) {
|
||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
const void *to_ptr = &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) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
||||
return REDIS_ERR;
|
||||
@ -332,6 +328,38 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) {
|
||||
/* Same timeval struct, short circuit */
|
||||
if (c->connect_timeout == timeout)
|
||||
return REDIS_OK;
|
||||
|
||||
/* Allocate context timeval if we need to */
|
||||
if (c->connect_timeout == NULL) {
|
||||
c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout));
|
||||
if (c->connect_timeout == NULL)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout));
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) {
|
||||
/* Same timeval struct, short circuit */
|
||||
if (c->command_timeout == timeout)
|
||||
return REDIS_OK;
|
||||
|
||||
/* Allocate context timeval if we need to */
|
||||
if (c->command_timeout == NULL) {
|
||||
c->command_timeout = hi_malloc(sizeof(*c->command_timeout));
|
||||
if (c->command_timeout == NULL)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout));
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
const struct timeval *timeout,
|
||||
const char *source_addr) {
|
||||
@ -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.
|
||||
**/
|
||||
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 (c->timeout != timeout) {
|
||||
if (c->timeout == NULL)
|
||||
c->timeout = malloc(sizeof(struct timeval));
|
||||
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
|
||||
goto oom;
|
||||
} else {
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
hi_free(c->connect_timeout);
|
||||
c->connect_timeout = NULL;
|
||||
}
|
||||
|
||||
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) {
|
||||
free(c->tcp.source_addr);
|
||||
hi_free(c->tcp.source_addr);
|
||||
c->tcp.source_addr = NULL;
|
||||
} else if (c->tcp.source_addr != source_addr) {
|
||||
free(c->tcp.source_addr);
|
||||
c->tcp.source_addr = strdup(source_addr);
|
||||
hi_free(c->tcp.source_addr);
|
||||
c->tcp.source_addr = hi_strdup(source_addr);
|
||||
}
|
||||
|
||||
snprintf(_port, 6, "%d", port);
|
||||
@ -446,8 +472,11 @@ addrretry:
|
||||
}
|
||||
|
||||
/* For repeat connection */
|
||||
free(c->saddr);
|
||||
c->saddr = malloc(p->ai_addrlen);
|
||||
hi_free(c->saddr);
|
||||
c->saddr = hi_malloc(p->ai_addrlen);
|
||||
if (c->saddr == NULL)
|
||||
goto oom;
|
||||
|
||||
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
||||
c->addrlen = p->ai_addrlen;
|
||||
|
||||
@ -474,12 +503,12 @@ addrretry:
|
||||
wait_for_ready:
|
||||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||
goto error;
|
||||
if (redisSetTcpNoDelay(c) != REDIS_OK)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (blocking && redisSetBlocking(c,1) != REDIS_OK)
|
||||
goto error;
|
||||
if (redisSetTcpNoDelay(c) != REDIS_OK)
|
||||
goto error;
|
||||
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
rv = REDIS_OK;
|
||||
@ -492,6 +521,8 @@ addrretry:
|
||||
goto error;
|
||||
}
|
||||
|
||||
oom:
|
||||
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||
error:
|
||||
rv = REDIS_ERR;
|
||||
end:
|
||||
@ -525,25 +556,32 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
return REDIS_ERR;
|
||||
|
||||
c->connection_type = REDIS_CONN_UNIX;
|
||||
if (c->unix_sock.path != path)
|
||||
c->unix_sock.path = strdup(path);
|
||||
if (c->unix_sock.path != path) {
|
||||
hi_free(c->unix_sock.path);
|
||||
|
||||
c->unix_sock.path = hi_strdup(path);
|
||||
if (c->unix_sock.path == NULL)
|
||||
goto oom;
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
if (c->timeout != timeout) {
|
||||
if (c->timeout == NULL)
|
||||
c->timeout = malloc(sizeof(struct timeval));
|
||||
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
|
||||
goto oom;
|
||||
} else {
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
hi_free(c->connect_timeout);
|
||||
c->connect_timeout = NULL;
|
||||
}
|
||||
|
||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||
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);
|
||||
sa->sun_family = AF_UNIX;
|
||||
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;
|
||||
return REDIS_ERR;
|
||||
#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"
|
||||
|
||||
void redisNetClose(redisContext *c);
|
||||
int redisNetRead(redisContext *c, char *buf, size_t bufcap);
|
||||
int redisNetWrite(redisContext *c);
|
||||
ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap);
|
||||
ssize_t redisNetWrite(redisContext *c);
|
||||
|
||||
int redisCheckSocketError(redisContext *c);
|
||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
||||
@ -51,4 +51,6 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
int redisKeepAlive(redisContext *c, int interval);
|
||||
int redisCheckConnectDone(redisContext *c, int *completed);
|
||||
|
||||
int redisSetTcpNoDelay(redisContext *c);
|
||||
|
||||
#endif
|
||||
|
158
deps/hiredis/read.c
vendored
158
deps/hiredis/read.c
vendored
@ -42,10 +42,14 @@
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "alloc.h"
|
||||
#include "read.h"
|
||||
#include "sds.h"
|
||||
#include "win32.h"
|
||||
|
||||
/* Initial size of our nested reply stack and how much we grow it when needd */
|
||||
#define REDIS_READER_STACK_SIZE 9
|
||||
|
||||
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
size_t len;
|
||||
|
||||
@ -55,7 +59,7 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
}
|
||||
|
||||
/* Clear input buffer on errors. */
|
||||
sdsfree(r->buf);
|
||||
hi_sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
|
||||
@ -243,11 +247,12 @@ static void moveToNextTask(redisReader *r) {
|
||||
return;
|
||||
}
|
||||
|
||||
cur = &(r->rstack[r->ridx]);
|
||||
prv = &(r->rstack[r->ridx-1]);
|
||||
cur = r->task[r->ridx];
|
||||
prv = r->task[r->ridx-1];
|
||||
assert(prv->type == REDIS_REPLY_ARRAY ||
|
||||
prv->type == REDIS_REPLY_MAP ||
|
||||
prv->type == REDIS_REPLY_SET);
|
||||
prv->type == REDIS_REPLY_SET ||
|
||||
prv->type == REDIS_REPLY_PUSH);
|
||||
if (cur->idx == prv->elements-1) {
|
||||
r->ridx--;
|
||||
} else {
|
||||
@ -262,7 +267,7 @@ static void moveToNextTask(redisReader *r) {
|
||||
}
|
||||
|
||||
static int processLineItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
redisReadTask *cur = r->task[r->ridx];
|
||||
void *obj;
|
||||
char *p;
|
||||
int len;
|
||||
@ -297,7 +302,7 @@ static int processLineItem(redisReader *r) {
|
||||
if (strcasecmp(buf,",inf") == 0) {
|
||||
d = INFINITY; /* Positive infinite. */
|
||||
} else if (strcasecmp(buf,",-inf") == 0) {
|
||||
d = -INFINITY; /* Nevative infinite. */
|
||||
d = -INFINITY; /* Negative infinite. */
|
||||
} else {
|
||||
d = strtod((char*)buf,&eptr);
|
||||
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
||||
@ -344,7 +349,7 @@ static int processLineItem(redisReader *r) {
|
||||
}
|
||||
|
||||
static int processBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
redisReadTask *cur = r->task[r->ridx];
|
||||
void *obj = NULL;
|
||||
char *p, *s;
|
||||
long long len;
|
||||
@ -415,19 +420,43 @@ static int processBulkItem(redisReader *r) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int redisReaderGrow(redisReader *r) {
|
||||
redisReadTask **aux;
|
||||
int newlen;
|
||||
|
||||
/* Grow our stack size */
|
||||
newlen = r->tasks + REDIS_READER_STACK_SIZE;
|
||||
aux = hi_realloc(r->task, sizeof(*r->task) * newlen);
|
||||
if (aux == NULL)
|
||||
goto oom;
|
||||
|
||||
r->task = aux;
|
||||
|
||||
/* Allocate new tasks */
|
||||
for (; r->tasks < newlen; r->tasks++) {
|
||||
r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
|
||||
if (r->task[r->tasks] == NULL)
|
||||
goto oom;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
oom:
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Process the array, map and set types. */
|
||||
static int processAggregateItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
redisReadTask *cur = r->task[r->ridx];
|
||||
void *obj;
|
||||
char *p;
|
||||
long long elements;
|
||||
int root = 0, len;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 7 */
|
||||
if (r->ridx == 8) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"No support for nested multi bulk replies with depth > 7");
|
||||
return REDIS_ERR;
|
||||
if (r->ridx == r->tasks - 1) {
|
||||
if (redisReaderGrow(r) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
@ -439,7 +468,9 @@ static int processAggregateItem(redisReader *r) {
|
||||
|
||||
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,
|
||||
"Multi-bulk length out of range");
|
||||
return REDIS_ERR;
|
||||
@ -475,12 +506,12 @@ static int processAggregateItem(redisReader *r) {
|
||||
cur->elements = elements;
|
||||
cur->obj = obj;
|
||||
r->ridx++;
|
||||
r->rstack[r->ridx].type = -1;
|
||||
r->rstack[r->ridx].elements = -1;
|
||||
r->rstack[r->ridx].idx = 0;
|
||||
r->rstack[r->ridx].obj = NULL;
|
||||
r->rstack[r->ridx].parent = cur;
|
||||
r->rstack[r->ridx].privdata = r->privdata;
|
||||
r->task[r->ridx]->type = -1;
|
||||
r->task[r->ridx]->elements = -1;
|
||||
r->task[r->ridx]->idx = 0;
|
||||
r->task[r->ridx]->obj = NULL;
|
||||
r->task[r->ridx]->parent = cur;
|
||||
r->task[r->ridx]->privdata = r->privdata;
|
||||
} else {
|
||||
moveToNextTask(r);
|
||||
}
|
||||
@ -495,7 +526,7 @@ static int processAggregateItem(redisReader *r) {
|
||||
}
|
||||
|
||||
static int processItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
redisReadTask *cur = r->task[r->ridx];
|
||||
char *p;
|
||||
|
||||
/* check if we need to read type */
|
||||
@ -535,6 +566,9 @@ static int processItem(redisReader *r) {
|
||||
case '=':
|
||||
cur->type = REDIS_REPLY_VERB;
|
||||
break;
|
||||
case '>':
|
||||
cur->type = REDIS_REPLY_PUSH;
|
||||
break;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
@ -560,6 +594,7 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
case REDIS_REPLY_PUSH:
|
||||
return processAggregateItem(r);
|
||||
default:
|
||||
assert(NULL);
|
||||
@ -570,33 +605,57 @@ static int processItem(redisReader *r) {
|
||||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||
redisReader *r;
|
||||
|
||||
r = calloc(1,sizeof(redisReader));
|
||||
r = hi_calloc(1,sizeof(redisReader));
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->fn = fn;
|
||||
r->buf = sdsempty();
|
||||
r->maxbuf = REDIS_READER_MAX_BUF;
|
||||
if (r->buf == NULL) {
|
||||
free(r);
|
||||
return NULL;
|
||||
r->buf = hi_sdsempty();
|
||||
if (r->buf == NULL)
|
||||
goto oom;
|
||||
|
||||
r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task));
|
||||
if (r->task == NULL)
|
||||
goto oom;
|
||||
|
||||
for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) {
|
||||
r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
|
||||
if (r->task[r->tasks] == NULL)
|
||||
goto oom;
|
||||
}
|
||||
|
||||
r->fn = fn;
|
||||
r->maxbuf = REDIS_READER_MAX_BUF;
|
||||
r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS;
|
||||
r->ridx = -1;
|
||||
|
||||
return r;
|
||||
oom:
|
||||
redisReaderFree(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void redisReaderFree(redisReader *r) {
|
||||
if (r == NULL)
|
||||
return;
|
||||
|
||||
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
||||
r->fn->freeObject(r->reply);
|
||||
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) {
|
||||
sds newbuf;
|
||||
hisds newbuf;
|
||||
|
||||
/* Return early when this reader is in an erroneous state. */
|
||||
if (r->err)
|
||||
@ -605,26 +664,25 @@ int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
|
||||
/* Copy the provided buffer. */
|
||||
if (buf != NULL && len >= 1) {
|
||||
/* Destroy internal buffer when it is empty and is quite large. */
|
||||
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = sdsempty();
|
||||
if (r->len == 0 && r->maxbuf != 0 && hi_sdsavail(r->buf) > r->maxbuf) {
|
||||
hi_sdsfree(r->buf);
|
||||
r->buf = hi_sdsempty();
|
||||
if (r->buf == 0) goto oom;
|
||||
|
||||
r->pos = 0;
|
||||
|
||||
/* r->buf should not be NULL since we just free'd a larger one. */
|
||||
assert(r->buf != NULL);
|
||||
}
|
||||
|
||||
newbuf = sdscatlen(r->buf,buf,len);
|
||||
if (newbuf == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
newbuf = hi_sdscatlen(r->buf,buf,len);
|
||||
if (newbuf == NULL) goto oom;
|
||||
|
||||
r->buf = newbuf;
|
||||
r->len = sdslen(r->buf);
|
||||
r->len = hi_sdslen(r->buf);
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
oom:
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
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. */
|
||||
if (r->ridx == -1) {
|
||||
r->rstack[0].type = -1;
|
||||
r->rstack[0].elements = -1;
|
||||
r->rstack[0].idx = -1;
|
||||
r->rstack[0].obj = NULL;
|
||||
r->rstack[0].parent = NULL;
|
||||
r->rstack[0].privdata = r->privdata;
|
||||
r->task[0]->type = -1;
|
||||
r->task[0]->elements = -1;
|
||||
r->task[0]->idx = -1;
|
||||
r->task[0]->obj = NULL;
|
||||
r->task[0]->parent = NULL;
|
||||
r->task[0]->privdata = r->privdata;
|
||||
r->ridx = 0;
|
||||
}
|
||||
|
||||
@ -663,9 +721,9 @@ int redisReaderGetReply(redisReader *r, void **reply) {
|
||||
/* Discard part of the buffer when we've consumed at least 1k, to avoid
|
||||
* doing unnecessary calls to memmove() in sds.c. */
|
||||
if (r->pos >= 1024) {
|
||||
sdsrange(r->buf,r->pos,-1);
|
||||
if (hi_sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR;
|
||||
r->pos = 0;
|
||||
r->len = sdslen(r->buf);
|
||||
r->len = hi_sdslen(r->buf);
|
||||
}
|
||||
|
||||
/* 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_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
|
||||
extern "C" {
|
||||
@ -71,7 +75,7 @@ extern "C" {
|
||||
|
||||
typedef struct redisReadTask {
|
||||
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 */
|
||||
void *obj; /* holds user-generated value for a read task */
|
||||
struct redisReadTask *parent; /* parent task */
|
||||
@ -96,8 +100,11 @@ typedef struct redisReader {
|
||||
size_t pos; /* Buffer cursor */
|
||||
size_t len; /* Buffer length */
|
||||
size_t maxbuf; /* Max length of unused buffer */
|
||||
long long maxelements; /* Max multi-bulk elements */
|
||||
|
||||
redisReadTask **task;
|
||||
int tasks;
|
||||
|
||||
redisReadTask rstack[9];
|
||||
int ridx; /* Index of current read task */
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef __SDS_H
|
||||
#define __SDS_H
|
||||
#ifndef HIREDIS_SDS_H
|
||||
#define HIREDIS_SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
#define HI_SDS_MAX_PREALLOC (1024*1024)
|
||||
#ifdef _MSC_VER
|
||||
#define __attribute__(x)
|
||||
typedef long long ssize_t;
|
||||
#define SSIZE_MAX (LLONG_MAX >> 1)
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef char *sds;
|
||||
typedef char *hisds;
|
||||
|
||||
/* Note: sdshdr5 is never used, we just access the flags byte directly.
|
||||
* However is here to document the layout of type 5 SDS strings. */
|
||||
struct __attribute__ ((__packed__)) sdshdr5 {
|
||||
struct __attribute__ ((__packed__)) hisdshdr5 {
|
||||
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
|
||||
#ifndef __cplusplus
|
||||
char buf[];
|
||||
#endif
|
||||
};
|
||||
struct __attribute__ ((__packed__)) sdshdr8 {
|
||||
struct __attribute__ ((__packed__)) hisdshdr8 {
|
||||
uint8_t len; /* used */
|
||||
uint8_t alloc; /* excluding the header and null terminator */
|
||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||
@ -60,7 +62,7 @@ struct __attribute__ ((__packed__)) sdshdr8 {
|
||||
char buf[];
|
||||
#endif
|
||||
};
|
||||
struct __attribute__ ((__packed__)) sdshdr16 {
|
||||
struct __attribute__ ((__packed__)) hisdshdr16 {
|
||||
uint16_t len; /* used */
|
||||
uint16_t alloc; /* excluding the header and null terminator */
|
||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||
@ -68,7 +70,7 @@ struct __attribute__ ((__packed__)) sdshdr16 {
|
||||
char buf[];
|
||||
#endif
|
||||
};
|
||||
struct __attribute__ ((__packed__)) sdshdr32 {
|
||||
struct __attribute__ ((__packed__)) hisdshdr32 {
|
||||
uint32_t len; /* used */
|
||||
uint32_t alloc; /* excluding the header and null terminator */
|
||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||
@ -76,7 +78,7 @@ struct __attribute__ ((__packed__)) sdshdr32 {
|
||||
char buf[];
|
||||
#endif
|
||||
};
|
||||
struct __attribute__ ((__packed__)) sdshdr64 {
|
||||
struct __attribute__ ((__packed__)) hisdshdr64 {
|
||||
uint64_t len; /* used */
|
||||
uint64_t alloc; /* excluding the header and null terminator */
|
||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||
@ -85,203 +87,203 @@ struct __attribute__ ((__packed__)) sdshdr64 {
|
||||
#endif
|
||||
};
|
||||
|
||||
#define SDS_TYPE_5 0
|
||||
#define SDS_TYPE_8 1
|
||||
#define SDS_TYPE_16 2
|
||||
#define SDS_TYPE_32 3
|
||||
#define SDS_TYPE_64 4
|
||||
#define SDS_TYPE_MASK 7
|
||||
#define SDS_TYPE_BITS 3
|
||||
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
|
||||
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
|
||||
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
|
||||
#define HI_SDS_TYPE_5 0
|
||||
#define HI_SDS_TYPE_8 1
|
||||
#define HI_SDS_TYPE_16 2
|
||||
#define HI_SDS_TYPE_32 3
|
||||
#define HI_SDS_TYPE_64 4
|
||||
#define HI_SDS_TYPE_MASK 7
|
||||
#define HI_SDS_TYPE_BITS 3
|
||||
#define HI_SDS_HDR_VAR(T,s) struct hisdshdr##T *sh = (struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T)));
|
||||
#define HI_SDS_HDR(T,s) ((struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T))))
|
||||
#define HI_SDS_TYPE_5_LEN(f) ((f)>>HI_SDS_TYPE_BITS)
|
||||
|
||||
static inline size_t sdslen(const sds s) {
|
||||
static inline size_t hi_sdslen(const hisds s) {
|
||||
unsigned char flags = s[-1];
|
||||
|
||||
switch(__builtin_expect((flags&SDS_TYPE_MASK), SDS_TYPE_5)) {
|
||||
case SDS_TYPE_5:
|
||||
return SDS_TYPE_5_LEN(flags);
|
||||
case SDS_TYPE_8:
|
||||
return SDS_HDR(8,s)->len;
|
||||
case SDS_TYPE_16:
|
||||
return SDS_HDR(16,s)->len;
|
||||
case SDS_TYPE_32:
|
||||
return SDS_HDR(32,s)->len;
|
||||
case SDS_TYPE_64:
|
||||
return SDS_HDR(64,s)->len;
|
||||
switch(__builtin_expect((flags&HI_SDS_TYPE_MASK), HI_SDS_TYPE_5)) {
|
||||
case HI_SDS_TYPE_5:
|
||||
return HI_SDS_TYPE_5_LEN(flags);
|
||||
case HI_SDS_TYPE_8:
|
||||
return HI_SDS_HDR(8,s)->len;
|
||||
case HI_SDS_TYPE_16:
|
||||
return HI_SDS_HDR(16,s)->len;
|
||||
case HI_SDS_TYPE_32:
|
||||
return HI_SDS_HDR(32,s)->len;
|
||||
case HI_SDS_TYPE_64:
|
||||
return HI_SDS_HDR(64,s)->len;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline size_t sdsavail(const sds s) {
|
||||
static inline size_t hi_sdsavail(const hisds s) {
|
||||
unsigned char flags = s[-1];
|
||||
switch(flags&SDS_TYPE_MASK) {
|
||||
case SDS_TYPE_5: {
|
||||
switch(flags&HI_SDS_TYPE_MASK) {
|
||||
case HI_SDS_TYPE_5: {
|
||||
return 0;
|
||||
}
|
||||
case SDS_TYPE_8: {
|
||||
SDS_HDR_VAR(8,s);
|
||||
case HI_SDS_TYPE_8: {
|
||||
HI_SDS_HDR_VAR(8,s);
|
||||
return sh->alloc - sh->len;
|
||||
}
|
||||
case SDS_TYPE_16: {
|
||||
SDS_HDR_VAR(16,s);
|
||||
case HI_SDS_TYPE_16: {
|
||||
HI_SDS_HDR_VAR(16,s);
|
||||
return sh->alloc - sh->len;
|
||||
}
|
||||
case SDS_TYPE_32: {
|
||||
SDS_HDR_VAR(32,s);
|
||||
case HI_SDS_TYPE_32: {
|
||||
HI_SDS_HDR_VAR(32,s);
|
||||
return sh->alloc - sh->len;
|
||||
}
|
||||
case SDS_TYPE_64: {
|
||||
SDS_HDR_VAR(64,s);
|
||||
case HI_SDS_TYPE_64: {
|
||||
HI_SDS_HDR_VAR(64,s);
|
||||
return sh->alloc - sh->len;
|
||||
}
|
||||
}
|
||||
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];
|
||||
switch(flags&SDS_TYPE_MASK) {
|
||||
case SDS_TYPE_5:
|
||||
switch(flags&HI_SDS_TYPE_MASK) {
|
||||
case HI_SDS_TYPE_5:
|
||||
{
|
||||
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;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->len = (uint8_t)newlen;
|
||||
case HI_SDS_TYPE_8:
|
||||
HI_SDS_HDR(8,s)->len = (uint8_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->len = (uint16_t)newlen;
|
||||
case HI_SDS_TYPE_16:
|
||||
HI_SDS_HDR(16,s)->len = (uint16_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->len = (uint32_t)newlen;
|
||||
case HI_SDS_TYPE_32:
|
||||
HI_SDS_HDR(32,s)->len = (uint32_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len = (uint64_t)newlen;
|
||||
case HI_SDS_TYPE_64:
|
||||
HI_SDS_HDR(64,s)->len = (uint64_t)newlen;
|
||||
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];
|
||||
switch(flags&SDS_TYPE_MASK) {
|
||||
case SDS_TYPE_5:
|
||||
switch(flags&HI_SDS_TYPE_MASK) {
|
||||
case HI_SDS_TYPE_5:
|
||||
{
|
||||
unsigned char *fp = ((unsigned char*)s)-1;
|
||||
unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
|
||||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
||||
unsigned char newlen = HI_SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
|
||||
*fp = HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS);
|
||||
}
|
||||
break;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->len += (uint8_t)inc;
|
||||
case HI_SDS_TYPE_8:
|
||||
HI_SDS_HDR(8,s)->len += (uint8_t)inc;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->len += (uint16_t)inc;
|
||||
case HI_SDS_TYPE_16:
|
||||
HI_SDS_HDR(16,s)->len += (uint16_t)inc;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->len += (uint32_t)inc;
|
||||
case HI_SDS_TYPE_32:
|
||||
HI_SDS_HDR(32,s)->len += (uint32_t)inc;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len += (uint64_t)inc;
|
||||
case HI_SDS_TYPE_64:
|
||||
HI_SDS_HDR(64,s)->len += (uint64_t)inc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* sdsalloc() = sdsavail() + sdslen() */
|
||||
static inline size_t sdsalloc(const sds s) {
|
||||
/* hi_sdsalloc() = hi_sdsavail() + hi_sdslen() */
|
||||
static inline size_t hi_sdsalloc(const hisds s) {
|
||||
unsigned char flags = s[-1];
|
||||
switch(flags&SDS_TYPE_MASK) {
|
||||
case SDS_TYPE_5:
|
||||
return SDS_TYPE_5_LEN(flags);
|
||||
case SDS_TYPE_8:
|
||||
return SDS_HDR(8,s)->alloc;
|
||||
case SDS_TYPE_16:
|
||||
return SDS_HDR(16,s)->alloc;
|
||||
case SDS_TYPE_32:
|
||||
return SDS_HDR(32,s)->alloc;
|
||||
case SDS_TYPE_64:
|
||||
return SDS_HDR(64,s)->alloc;
|
||||
switch(flags & HI_SDS_TYPE_MASK) {
|
||||
case HI_SDS_TYPE_5:
|
||||
return HI_SDS_TYPE_5_LEN(flags);
|
||||
case HI_SDS_TYPE_8:
|
||||
return HI_SDS_HDR(8,s)->alloc;
|
||||
case HI_SDS_TYPE_16:
|
||||
return HI_SDS_HDR(16,s)->alloc;
|
||||
case HI_SDS_TYPE_32:
|
||||
return HI_SDS_HDR(32,s)->alloc;
|
||||
case HI_SDS_TYPE_64:
|
||||
return HI_SDS_HDR(64,s)->alloc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void sdssetalloc(sds s, size_t newlen) {
|
||||
static inline void hi_sdssetalloc(hisds s, size_t newlen) {
|
||||
unsigned char flags = s[-1];
|
||||
switch(flags&SDS_TYPE_MASK) {
|
||||
case SDS_TYPE_5:
|
||||
switch(flags&HI_SDS_TYPE_MASK) {
|
||||
case HI_SDS_TYPE_5:
|
||||
/* Nothing to do, this type has no total allocation info. */
|
||||
break;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->alloc = (uint8_t)newlen;
|
||||
case HI_SDS_TYPE_8:
|
||||
HI_SDS_HDR(8,s)->alloc = (uint8_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->alloc = (uint16_t)newlen;
|
||||
case HI_SDS_TYPE_16:
|
||||
HI_SDS_HDR(16,s)->alloc = (uint16_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->alloc = (uint32_t)newlen;
|
||||
case HI_SDS_TYPE_32:
|
||||
HI_SDS_HDR(32,s)->alloc = (uint32_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->alloc = (uint64_t)newlen;
|
||||
case HI_SDS_TYPE_64:
|
||||
HI_SDS_HDR(64,s)->alloc = (uint64_t)newlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sds sdsnewlen(const void *init, size_t initlen);
|
||||
sds sdsnew(const char *init);
|
||||
sds sdsempty(void);
|
||||
sds sdsdup(const sds s);
|
||||
void sdsfree(sds s);
|
||||
sds sdsgrowzero(sds s, size_t len);
|
||||
sds sdscatlen(sds s, const void *t, size_t len);
|
||||
sds sdscat(sds s, const char *t);
|
||||
sds sdscatsds(sds s, const sds t);
|
||||
sds sdscpylen(sds s, const char *t, size_t len);
|
||||
sds sdscpy(sds s, const char *t);
|
||||
hisds hi_sdsnewlen(const void *init, size_t initlen);
|
||||
hisds hi_sdsnew(const char *init);
|
||||
hisds hi_sdsempty(void);
|
||||
hisds hi_sdsdup(const hisds s);
|
||||
void hi_sdsfree(hisds s);
|
||||
hisds hi_sdsgrowzero(hisds s, size_t len);
|
||||
hisds hi_sdscatlen(hisds s, const void *t, size_t len);
|
||||
hisds hi_sdscat(hisds s, const char *t);
|
||||
hisds hi_sdscatsds(hisds s, const hisds t);
|
||||
hisds hi_sdscpylen(hisds s, const char *t, size_t len);
|
||||
hisds hi_sdscpy(hisds s, const char *t);
|
||||
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
|
||||
hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap);
|
||||
#ifdef __GNUC__
|
||||
sds sdscatprintf(sds s, const char *fmt, ...)
|
||||
hisds hi_sdscatprintf(hisds s, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
#else
|
||||
sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
hisds hi_sdscatprintf(hisds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdscatfmt(sds s, char const *fmt, ...);
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
int sdscmp(const sds s1, const sds s2);
|
||||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
|
||||
void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, const char *p, size_t len);
|
||||
sds *sdssplitargs(const char *line, int *argc);
|
||||
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
|
||||
sds sdsjoin(char **argv, int argc, char *sep);
|
||||
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
|
||||
hisds hi_sdscatfmt(hisds s, char const *fmt, ...);
|
||||
hisds hi_sdstrim(hisds s, const char *cset);
|
||||
int hi_sdsrange(hisds s, ssize_t start, ssize_t end);
|
||||
void hi_sdsupdatelen(hisds s);
|
||||
void hi_sdsclear(hisds s);
|
||||
int hi_sdscmp(const hisds s1, const hisds s2);
|
||||
hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
|
||||
void hi_sdsfreesplitres(hisds *tokens, int count);
|
||||
void hi_sdstolower(hisds s);
|
||||
void hi_sdstoupper(hisds s);
|
||||
hisds hi_sdsfromlonglong(long long value);
|
||||
hisds hi_sdscatrepr(hisds s, const char *p, size_t len);
|
||||
hisds *hi_sdssplitargs(const char *line, int *argc);
|
||||
hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen);
|
||||
hisds hi_sdsjoin(char **argv, int argc, char *sep);
|
||||
hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen);
|
||||
|
||||
/* Low level functions exposed to the user API */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen);
|
||||
void sdsIncrLen(sds s, int incr);
|
||||
sds sdsRemoveFreeSpace(sds s);
|
||||
size_t sdsAllocSize(sds s);
|
||||
void *sdsAllocPtr(sds s);
|
||||
hisds hi_sdsMakeRoomFor(hisds s, size_t addlen);
|
||||
void hi_sdsIncrLen(hisds s, int incr);
|
||||
hisds hi_sdsRemoveFreeSpace(hisds s);
|
||||
size_t hi_sdsAllocSize(hisds s);
|
||||
void *hi_sdsAllocPtr(hisds s);
|
||||
|
||||
/* Export the allocator used by SDS to the program using SDS.
|
||||
* Sometimes the program SDS is linked to, may use a different set of
|
||||
* allocators, but may want to allocate or free things that SDS will
|
||||
* respectively free or allocate. */
|
||||
void *sds_malloc(size_t size);
|
||||
void *sds_realloc(void *ptr, size_t size);
|
||||
void sds_free(void *ptr);
|
||||
void *hi_sds_malloc(size_t size);
|
||||
void *hi_sds_realloc(void *ptr, size_t size);
|
||||
void hi_sds_free(void *ptr);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int sdsTest(int argc, char *argv[]);
|
||||
int hi_sdsTest(int argc, char *argv[]);
|
||||
#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
|
||||
* to use the default libc allocator). */
|
||||
|
||||
#define s_malloc malloc
|
||||
#define s_realloc realloc
|
||||
#define s_free free
|
||||
#include "alloc.h"
|
||||
|
||||
#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 ret = 0;
|
||||
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;
|
||||
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
|
||||
} else {
|
||||
|
3
deps/hiredis/sockcompat.h
vendored
3
deps/hiredis/sockcompat.h
vendored
@ -49,9 +49,10 @@
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
typedef signed long ssize_t;
|
||||
typedef long long ssize_t;
|
||||
#endif
|
||||
|
||||
/* 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 <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "win32.h"
|
||||
#include "async_private.h"
|
||||
#include "hiredis_ssl.h"
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
/* The SSL context is attached to SSL/TLS connections as a privdata. */
|
||||
typedef struct redisSSLContext {
|
||||
/**
|
||||
* OpenSSL SSL_CTX; It is optional and will not be set when using
|
||||
* user-supplied SSL.
|
||||
*/
|
||||
struct redisSSLContext {
|
||||
/* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
|
||||
SSL_CTX *ssl_ctx;
|
||||
|
||||
/* Requested SNI, or NULL */
|
||||
char *server_name;
|
||||
};
|
||||
|
||||
/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
|
||||
typedef struct redisSSL {
|
||||
/**
|
||||
* OpenSSL SSL object.
|
||||
*/
|
||||
@ -72,43 +80,11 @@ typedef struct redisSSLContext {
|
||||
* should resume whenever a read takes place, if possible
|
||||
*/
|
||||
int pendingWrite;
|
||||
} redisSSLContext;
|
||||
} redisSSL;
|
||||
|
||||
/* Forward declaration */
|
||||
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.
|
||||
* 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
|
||||
|
||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||
#ifdef _WIN32
|
||||
typedef CRITICAL_SECTION sslLockType;
|
||||
static void sslLockInit(sslLockType* l) {
|
||||
InitializeCriticalSection(l);
|
||||
}
|
||||
static void sslLockAcquire(sslLockType* l) {
|
||||
EnterCriticalSection(l);
|
||||
}
|
||||
static void sslLockRelease(sslLockType* l) {
|
||||
LeaveCriticalSection(l);
|
||||
}
|
||||
#else
|
||||
typedef pthread_mutex_t sslLockType;
|
||||
static void sslLockInit(sslLockType *l) {
|
||||
pthread_mutex_init(l, NULL);
|
||||
@ -129,7 +117,9 @@ static void sslLockAcquire(sslLockType *l) {
|
||||
static void sslLockRelease(sslLockType *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) {
|
||||
sslLockType *l = ossl_locks + lkid;
|
||||
@ -144,36 +134,151 @@ static void opensslDoLock(int mode, int lkid, const char *f, int line) {
|
||||
(void)line;
|
||||
}
|
||||
|
||||
static void initOpensslLocks(void) {
|
||||
static int initOpensslLocks(void) {
|
||||
unsigned ii, nlocks;
|
||||
if (CRYPTO_get_locking_callback() != NULL) {
|
||||
/* Someone already set the callback before us. Don't destroy it! */
|
||||
return;
|
||||
return REDIS_OK;
|
||||
}
|
||||
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++) {
|
||||
sslLockInit(ossl_locks + ii);
|
||||
}
|
||||
CRYPTO_set_locking_callback(opensslDoLock);
|
||||
return REDIS_OK;
|
||||
}
|
||||
#endif /* HIREDIS_USE_CRYPTO_LOCKS */
|
||||
|
||||
int redisInitOpenSSL(void)
|
||||
{
|
||||
SSL_library_init();
|
||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||
initOpensslLocks();
|
||||
#endif
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* redisSSLContext helper context destruction.
|
||||
*/
|
||||
|
||||
const char *redisSSLContextGetError(redisSSLContextError error)
|
||||
{
|
||||
switch (error) {
|
||||
case REDIS_SSL_CTX_NONE:
|
||||
return "No Error";
|
||||
case REDIS_SSL_CTX_CREATE_FAILED:
|
||||
return "Failed to create OpenSSL SSL_CTX";
|
||||
case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
|
||||
return "Client cert and key must both be specified or skipped";
|
||||
case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
|
||||
return "Failed to load CA Certificate or CA Path";
|
||||
case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
|
||||
return "Failed to load client certificate";
|
||||
case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
|
||||
return "Failed to load private key";
|
||||
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.
|
||||
*/
|
||||
|
||||
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");
|
||||
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;
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
|
||||
rssl->ssl_ctx = ssl_ctx;
|
||||
rssl->ssl = ssl;
|
||||
|
||||
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();
|
||||
int rv = SSL_connect(rssl->ssl);
|
||||
if (rv == 1) {
|
||||
c->privctx = rssl;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
rv = SSL_get_error(rssl->ssl, rv);
|
||||
if (((c->flags & REDIS_BLOCK) == 0) &&
|
||||
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
|
||||
c->privctx = rssl;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@ -203,83 +310,58 @@ static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
|
||||
}
|
||||
__redisSetError(c, REDIS_ERR_IO, err);
|
||||
}
|
||||
|
||||
hi_free(rssl);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around redisSSLConnect() for users who manage their own context and
|
||||
* create their own SSL object.
|
||||
*/
|
||||
|
||||
int redisInitiateSSL(redisContext *c, SSL *ssl) {
|
||||
return redisSSLConnect(c, 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;
|
||||
SSL *ssl = NULL;
|
||||
int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
|
||||
{
|
||||
if (!c || !redis_ssl_ctx)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Initialize global OpenSSL stuff */
|
||||
static int isInit = 0;
|
||||
if (!isInit) {
|
||||
isInit = 1;
|
||||
SSL_library_init();
|
||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||
initOpensslLocks();
|
||||
#endif
|
||||
}
|
||||
/* We want to verify that redisSSLConnect() won't fail on this, as it will
|
||||
* not own the SSL object in that case and we'll end up leaking.
|
||||
*/
|
||||
if (c->privctx)
|
||||
return REDIS_ERR;
|
||||
|
||||
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||
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);
|
||||
SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
|
||||
if (!ssl) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
|
||||
goto error;
|
||||
}
|
||||
if (servername) {
|
||||
if (!SSL_set_tlsext_host_name(ssl, servername)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
|
||||
|
||||
if (redis_ssl_ctx->server_name) {
|
||||
if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
return redisSSLConnect(c, ssl_ctx, ssl);
|
||||
return redisSSLConnect(c, ssl);
|
||||
|
||||
error:
|
||||
if (ssl) SSL_free(ssl);
|
||||
if (ssl_ctx) SSL_CTX_free(ssl_ctx);
|
||||
if (ssl)
|
||||
SSL_free(ssl);
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
|
||||
static void redisSSLFreeContext(void *privdata){
|
||||
redisSSLContext *rsc = privdata;
|
||||
static void redisSSLFree(void *privctx){
|
||||
redisSSL *rsc = privctx;
|
||||
|
||||
if (!rsc) return;
|
||||
if (rsc->ssl) {
|
||||
SSL_free(rsc->ssl);
|
||||
rsc->ssl = NULL;
|
||||
}
|
||||
if (rsc->ssl_ctx) {
|
||||
SSL_CTX_free(rsc->ssl_ctx);
|
||||
rsc->ssl_ctx = NULL;
|
||||
}
|
||||
free(rsc);
|
||||
hi_free(rsc);
|
||||
}
|
||||
|
||||
static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
redisSSL *rssl = c->privctx;
|
||||
|
||||
int nread = SSL_read(rssl->ssl, buf, bufcap);
|
||||
if (nread > 0) {
|
||||
@ -356,10 +434,10 @@ static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
}
|
||||
}
|
||||
|
||||
static int redisSSLWrite(redisContext *c) {
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
static ssize_t redisSSLWrite(redisContext *c) {
|
||||
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);
|
||||
|
||||
if (rv > 0) {
|
||||
@ -380,7 +458,7 @@ static int redisSSLWrite(redisContext *c) {
|
||||
|
||||
static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
||||
int rv;
|
||||
redisSSLContext *rssl = ac->c.privdata;
|
||||
redisSSL *rssl = ac->c.privctx;
|
||||
redisContext *c = &ac->c;
|
||||
|
||||
rssl->wantRead = 0;
|
||||
@ -410,7 +488,7 @@ static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
||||
|
||||
static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
||||
int rv, done = 0;
|
||||
redisSSLContext *rssl = ac->c.privdata;
|
||||
redisSSL *rssl = ac->c.privctx;
|
||||
redisContext *c = &ac->c;
|
||||
|
||||
rssl->pendingWrite = 0;
|
||||
@ -439,7 +517,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
||||
}
|
||||
|
||||
redisContextFuncs redisContextSSLFuncs = {
|
||||
.free_privdata = redisSSLFreeContext,
|
||||
.free_privctx = redisSSLFree,
|
||||
.async_read = redisSSLAsyncRead,
|
||||
.async_write = redisSSLAsyncWrite,
|
||||
.read = redisSSLRead,
|
||||
|
581
deps/hiredis/test.c
vendored
581
deps/hiredis/test.c
vendored
@ -1,22 +1,24 @@
|
||||
#include "fmacros.h"
|
||||
#include "sockcompat.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef _WIN32
|
||||
#include <strings.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
#include "hiredis_ssl.h"
|
||||
#endif
|
||||
#include "net.h"
|
||||
#include "win32.h"
|
||||
|
||||
enum connection_type {
|
||||
CONN_TCP,
|
||||
@ -47,15 +49,35 @@ struct config {
|
||||
} 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" :) */
|
||||
static int tests = 0, fails = 0;
|
||||
static int tests = 0, fails = 0, skips = 0;
|
||||
#define test(_s) { printf("#%02d ", ++tests); printf(_s); }
|
||||
#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
|
||||
#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
|
||||
|
||||
static long long usec(void) {
|
||||
#ifndef _MSC_VER
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,NULL);
|
||||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
||||
#else
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The assert() calls below have side effects, so we need assert()
|
||||
@ -65,6 +87,43 @@ static long long usec(void) {
|
||||
#define assert(e) (void)(e)
|
||||
#endif
|
||||
|
||||
/* Helper to extract Redis version information. Aborts on any failure. */
|
||||
#define REDIS_VERSION_FIELD "redis_version:"
|
||||
void get_redis_version(redisContext *c, int *majorptr, int *minorptr) {
|
||||
redisReply *reply;
|
||||
char *eptr, *s, *e;
|
||||
int major, minor;
|
||||
|
||||
reply = redisCommand(c, "INFO");
|
||||
if (reply == NULL || c->err || reply->type != REDIS_REPLY_STRING)
|
||||
goto abort;
|
||||
if ((s = strstr(reply->str, REDIS_VERSION_FIELD)) == NULL)
|
||||
goto abort;
|
||||
|
||||
s += strlen(REDIS_VERSION_FIELD);
|
||||
|
||||
/* We need a field terminator and at least 'x.y.z' (5) bytes of data */
|
||||
if ((e = strstr(s, "\r\n")) == NULL || (e - s) < 5)
|
||||
goto abort;
|
||||
|
||||
/* Extract version info */
|
||||
major = strtol(s, &eptr, 10);
|
||||
if (*eptr != '.') goto abort;
|
||||
minor = strtol(eptr+1, NULL, 10);
|
||||
|
||||
/* Push info the caller wants */
|
||||
if (majorptr) *majorptr = major;
|
||||
if (minorptr) *minorptr = minor;
|
||||
|
||||
freeReplyObject(reply);
|
||||
return;
|
||||
|
||||
abort:
|
||||
freeReplyObject(reply);
|
||||
fprintf(stderr, "Error: Cannot determine Redis version, aborting\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static redisContext *select_database(redisContext *c) {
|
||||
redisReply *reply;
|
||||
|
||||
@ -87,6 +146,26 @@ static redisContext *select_database(redisContext *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) {
|
||||
redisReply *reply;
|
||||
|
||||
@ -105,9 +184,9 @@ static int disconnect(redisContext *c, int keep_fd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void do_ssl_handshake(redisContext *c, struct config config) {
|
||||
static void do_ssl_handshake(redisContext *c) {
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL);
|
||||
redisInitiateSSLWithContext(c, _ssl_ctx);
|
||||
if (c->err) {
|
||||
printf("SSL error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
@ -115,7 +194,6 @@ static void do_ssl_handshake(redisContext *c, struct config config) {
|
||||
}
|
||||
#else
|
||||
(void) c;
|
||||
(void) config;
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -150,7 +228,7 @@ static redisContext *do_connect(struct config config) {
|
||||
}
|
||||
|
||||
if (config.type == CONN_SSL) {
|
||||
do_ssl_handshake(c, config);
|
||||
do_ssl_handshake(c);
|
||||
}
|
||||
|
||||
return select_database(c);
|
||||
@ -160,7 +238,7 @@ static void do_reconnect(redisContext *c, struct config config) {
|
||||
redisReconnect(c);
|
||||
|
||||
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");
|
||||
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));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
test("Format command with %%s string interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
test("Format command with %%s and an empty string: ");
|
||||
len = redisFormatCommand(&cmd,"SET %s %s","foo","");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
test("Format command with an empty string in between proper interpolations: ");
|
||||
len = redisFormatCommand(&cmd,"SET %s %s","","foo");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(0+2)+4+(3+2));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
test("Format command with %%b string interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
test("Format command with %%b and an empty string: ");
|
||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
test("Format command with literal %%: ");
|
||||
len = redisFormatCommand(&cmd,"SET %% %%");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(1+2)+4+(1+2));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
/* Vararg width depends on the type. These tests make sure that the
|
||||
* width is correctly determined using the format and subsequent varargs
|
||||
@ -219,7 +297,7 @@ static void test_format_commands(void) {
|
||||
len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
|
||||
test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
|
||||
len == 4+5+(12+2)+4+(9+2)); \
|
||||
free(cmd); \
|
||||
hi_free(cmd); \
|
||||
} while(0)
|
||||
|
||||
#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"); \
|
||||
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)); \
|
||||
free(cmd); \
|
||||
hi_free(cmd); \
|
||||
} while(0)
|
||||
|
||||
INTEGER_WIDTH_TEST("d", int);
|
||||
@ -259,29 +337,29 @@ static void test_format_commands(void) {
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
test("Format command by passing argc/argv with lengths: ");
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,lens);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
|
||||
sds sds_cmd;
|
||||
hisds sds_cmd;
|
||||
|
||||
sds_cmd = sdsempty();
|
||||
test("Format command into sds by passing argc/argv without lengths: ");
|
||||
sds_cmd = NULL;
|
||||
test("Format command into hisds by passing argc/argv without lengths: ");
|
||||
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
|
||||
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
sdsfree(sds_cmd);
|
||||
hi_sdsfree(sds_cmd);
|
||||
|
||||
sds_cmd = sdsempty();
|
||||
test("Format command into sds by passing argc/argv with lengths: ");
|
||||
sds_cmd = NULL;
|
||||
test("Format command into hisds by passing argc/argv with lengths: ");
|
||||
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
|
||||
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||
sdsfree(sds_cmd);
|
||||
hi_sdsfree(sds_cmd);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
free(cmd);
|
||||
hi_free(cmd);
|
||||
freeReplyObject(reply);
|
||||
|
||||
disconnect(c, 0);
|
||||
@ -308,7 +386,7 @@ static void test_append_formatted_commands(struct config config) {
|
||||
|
||||
static void test_reply_reader(void) {
|
||||
redisReader *reader;
|
||||
void *reply;
|
||||
void *reply, *root;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
@ -332,16 +410,26 @@ static void test_reply_reader(void) {
|
||||
strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error on nested multi bulks with depth > 7: ");
|
||||
reader = redisReaderCreate();
|
||||
|
||||
for (i = 0; i < 9; i++) {
|
||||
redisReaderFeed(reader,(char*)"*1\r\n",4);
|
||||
test("Can handle arbitrarily nested multi-bulks: ");
|
||||
for (i = 0; i < 128; i++) {
|
||||
redisReaderFeed(reader,(char*)"*1\r\n", 4);
|
||||
}
|
||||
redisReaderFeed(reader,(char*)"$6\r\nLOLWUT\r\n",12);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
root = reply; /* Keep track of the root reply */
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
|
||||
((redisReply*)reply)->elements == 1);
|
||||
|
||||
ret = redisReaderGetReply(reader,NULL);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strncasecmp(reader->errstr,"No support for",14) == 0);
|
||||
test("Can parse arbitrarily nested multi-bulks correctly: ");
|
||||
while(i--) {
|
||||
assert(reply != NULL && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY);
|
||||
reply = ((redisReply*)reply)->element[0];
|
||||
}
|
||||
test_cond(((redisReply*)reply)->type == REDIS_REPLY_STRING &&
|
||||
!memcmp(((redisReply*)reply)->str, "LOLWUT", 6));
|
||||
freeReplyObject(root);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses LLONG_MAX: ");
|
||||
@ -400,6 +488,16 @@ static void test_reply_reader(void) {
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can configure maximum multi-bulk elements: ");
|
||||
reader = redisReaderCreate();
|
||||
reader->maxelements = 1024;
|
||||
redisReaderFeed(reader, "*1025\r\n", 7);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr, "Multi-bulk length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when array > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
@ -459,6 +557,32 @@ static void test_reply_reader(void) {
|
||||
((redisReply*)reply)->elements == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
/* RESP3 verbatim strings (GitHub issue #802) */
|
||||
test("Can parse RESP3 verbatim strings: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader,(char*)"=10\r\ntxt:LOLWUT\r\n",17);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_VERB &&
|
||||
!memcmp(((redisReply*)reply)->str,"LOLWUT", 6));
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
/* RESP3 push messages (Github issue #815) */
|
||||
test("Can parse RESP3 push messages: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader,(char*)">2\r\n$6\r\nLOLWUT\r\n:42\r\n",21);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_PUSH &&
|
||||
((redisReply*)reply)->elements == 2 &&
|
||||
((redisReply*)reply)->element[0]->type == REDIS_REPLY_STRING &&
|
||||
!memcmp(((redisReply*)reply)->element[0]->str,"LOLWUT",6) &&
|
||||
((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->element[1]->integer == 42);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
}
|
||||
|
||||
static void test_free_null(void) {
|
||||
@ -474,6 +598,47 @@ static void test_free_null(void) {
|
||||
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"
|
||||
static void test_blocking_connection_errors(void) {
|
||||
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, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
|
||||
strcmp(c->errstr, "Name does not resolve") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
||||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "no address associated with name") == 0));
|
||||
strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "no address associated with name") == 0 ||
|
||||
strcmp(c->errstr, "No such host is known. ") == 0));
|
||||
redisFree(c);
|
||||
} else {
|
||||
printf("Skipping NXDOMAIN test. Found evil ISP!\n");
|
||||
freeaddrinfo(ai_tmp);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
test("Returns error when the port is not open: ");
|
||||
c = redisConnect((char*)"localhost", 1);
|
||||
test_cond(c->err == REDIS_ERR_IO &&
|
||||
@ -514,11 +679,166 @@ static void test_blocking_connection_errors(void) {
|
||||
c = redisConnectUnix((char*)"/tmp/idontexist.sock");
|
||||
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
|
||||
redisFree(c);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Test push handler */
|
||||
void push_handler(void *privdata, void *r) {
|
||||
struct pushCounters *pcounts = privdata;
|
||||
redisReply *reply = r, *payload;
|
||||
|
||||
assert(reply && reply->type == REDIS_REPLY_PUSH && reply->elements == 2);
|
||||
|
||||
payload = reply->element[1];
|
||||
if (payload->type == REDIS_REPLY_ARRAY) {
|
||||
payload = payload->element[0];
|
||||
}
|
||||
|
||||
if (payload->type == REDIS_REPLY_STRING) {
|
||||
pcounts->str++;
|
||||
} else if (payload->type == REDIS_REPLY_NIL) {
|
||||
pcounts->nil++;
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
/* Dummy function just to test setting a callback with redisOptions */
|
||||
void push_handler_async(redisAsyncContext *ac, void *reply) {
|
||||
(void)ac;
|
||||
(void)reply;
|
||||
}
|
||||
|
||||
static void test_resp3_push_handler(redisContext *c) {
|
||||
struct pushCounters pc = {0};
|
||||
redisPushFn *old = NULL;
|
||||
redisReply *reply;
|
||||
void *privdata;
|
||||
|
||||
/* Switch to RESP3 and turn on client tracking */
|
||||
send_hello(c, 3);
|
||||
send_client_tracking(c, "ON");
|
||||
privdata = c->privdata;
|
||||
c->privdata = &pc;
|
||||
|
||||
reply = redisCommand(c, "GET key:0");
|
||||
assert(reply != NULL);
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("RESP3 PUSH messages are handled out of band by default: ");
|
||||
reply = redisCommand(c, "SET key:0 val:0");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
|
||||
freeReplyObject(reply);
|
||||
|
||||
assert((reply = redisCommand(c, "GET key:0")) != NULL);
|
||||
freeReplyObject(reply);
|
||||
|
||||
old = redisSetPushCallback(c, push_handler);
|
||||
test("We can set a custom RESP3 PUSH handler: ");
|
||||
reply = redisCommand(c, "SET key:0 val:0");
|
||||
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) {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
int major;
|
||||
|
||||
c = do_connect(config);
|
||||
|
||||
@ -591,14 +911,42 @@ static void test_blocking_connection(struct config config) {
|
||||
strcasecmp(reply->element[1]->str,"pong") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Make sure passing NULL to redisGetReply is safe */
|
||||
test("Can pass NULL to redisGetReply: ");
|
||||
assert(redisAppendCommand(c, "PING") == REDIS_OK);
|
||||
test_cond(redisGetReply(c, NULL) == REDIS_OK);
|
||||
|
||||
get_redis_version(c, &major, NULL);
|
||||
if (major >= 6) test_resp3_push_handler(c);
|
||||
test_resp3_push_options(config);
|
||||
|
||||
test_privdata_hooks(config);
|
||||
|
||||
disconnect(c, 0);
|
||||
}
|
||||
|
||||
/* Send DEBUG SLEEP 0 to detect if we have this command */
|
||||
static int detect_debug_sleep(redisContext *c) {
|
||||
int detected;
|
||||
redisReply *reply = redisCommand(c, "DEBUG SLEEP 0\r\n");
|
||||
|
||||
if (reply == NULL || c->err) {
|
||||
const char *cause = c->err ? c->errstr : "(none)";
|
||||
fprintf(stderr, "Error testing for DEBUG SLEEP (Redis error: %s), exiting\n", cause);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
detected = reply->type == REDIS_REPLY_STATUS;
|
||||
freeReplyObject(reply);
|
||||
|
||||
return detected;
|
||||
}
|
||||
|
||||
static void test_blocking_connection_timeouts(struct config config) {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
ssize_t s;
|
||||
const char *cmd = "DEBUG SLEEP 3\r\n";
|
||||
const char *sleep_cmd = "DEBUG SLEEP 3\r\n";
|
||||
struct timeval tv;
|
||||
|
||||
c = do_connect(config);
|
||||
@ -615,14 +963,24 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
|
||||
c = do_connect(config);
|
||||
test("Does not return a reply when the command times out: ");
|
||||
redisAppendFormattedCommand(c, cmd, strlen(cmd));
|
||||
s = c->funcs->write(c);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000;
|
||||
redisSetTimeout(c, tv);
|
||||
reply = redisCommand(c, "GET foo");
|
||||
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0);
|
||||
freeReplyObject(reply);
|
||||
if (detect_debug_sleep(c)) {
|
||||
redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
|
||||
s = c->funcs->write(c);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000;
|
||||
redisSetTimeout(c, tv);
|
||||
reply = redisCommand(c, "GET foo");
|
||||
#ifndef _WIN32
|
||||
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO &&
|
||||
strcmp(c->errstr, "Resource temporarily unavailable") == 0);
|
||||
#else
|
||||
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT &&
|
||||
strcmp(c->errstr, "recv timeout") == 0);
|
||||
#endif
|
||||
freeReplyObject(reply);
|
||||
} else {
|
||||
test_skipped();
|
||||
}
|
||||
|
||||
test("Reconnect properly reconnects after a timeout: ");
|
||||
do_reconnect(c, config);
|
||||
@ -649,18 +1007,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
|
||||
/* Connect to target given by config. */
|
||||
c = do_connect(config);
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
get_redis_version(c, &major, &minor);
|
||||
|
||||
test("Returns I/O error when the connection is lost: ");
|
||||
reply = redisCommand(c,"QUIT");
|
||||
@ -674,6 +1021,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
test_cond(reply == NULL);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
/* On 2.0, QUIT will cause the connection to be closed immediately and
|
||||
* the read(2) for the reply on QUIT will set the error to EOF.
|
||||
* On >2.0, QUIT will return with OK and another read(2) needed to be
|
||||
@ -681,14 +1029,19 @@ static void test_blocking_io_errors(struct config config) {
|
||||
* conditions, the error will be set to EOF. */
|
||||
assert(c->err == REDIS_ERR_EOF &&
|
||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||
#endif
|
||||
redisFree(c);
|
||||
|
||||
c = do_connect(config);
|
||||
test("Returns I/O error on socket timeout: ");
|
||||
struct timeval tv = { 0, 1000 };
|
||||
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
||||
test_cond(redisGetReply(c,&_reply) == REDIS_ERR &&
|
||||
c->err == REDIS_ERR_IO && errno == EAGAIN);
|
||||
int respcode = redisGetReply(c,&_reply);
|
||||
#ifndef _WIN32
|
||||
test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN);
|
||||
#else
|
||||
test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT);
|
||||
#endif
|
||||
redisFree(c);
|
||||
}
|
||||
|
||||
@ -716,6 +1069,18 @@ static void test_invalid_timeout_errors(struct config config) {
|
||||
redisFree(c);
|
||||
}
|
||||
|
||||
/* Wrap malloc to abort on failure so OOM checks don't make the test logic
|
||||
* harder to follow. */
|
||||
void *hi_malloc_safe(size_t size) {
|
||||
void *ptr = hi_malloc(size);
|
||||
if (ptr == NULL) {
|
||||
fprintf(stderr, "Error: Out of memory\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void test_throughput(struct config config) {
|
||||
redisContext *c = do_connect(config);
|
||||
redisReply **replies;
|
||||
@ -727,7 +1092,7 @@ static void test_throughput(struct config config) {
|
||||
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
||||
|
||||
num = 1000;
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
replies[i] = redisCommand(c,"PING");
|
||||
@ -735,10 +1100,10 @@ static void test_throughput(struct config config) {
|
||||
}
|
||||
t2 = usec();
|
||||
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);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
replies[i] = redisCommand(c,"LRANGE mylist 0 499");
|
||||
@ -747,10 +1112,10 @@ static void test_throughput(struct config config) {
|
||||
}
|
||||
t2 = usec();
|
||||
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);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
|
||||
@ -758,11 +1123,11 @@ static void test_throughput(struct config config) {
|
||||
}
|
||||
t2 = usec();
|
||||
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);
|
||||
|
||||
num = 10000;
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
redisAppendCommand(c,"PING");
|
||||
t1 = usec();
|
||||
@ -772,10 +1137,10 @@ static void test_throughput(struct config config) {
|
||||
}
|
||||
t2 = usec();
|
||||
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);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
redisAppendCommand(c,"LRANGE mylist 0 499");
|
||||
t1 = usec();
|
||||
@ -786,10 +1151,10 @@ static void test_throughput(struct config config) {
|
||||
}
|
||||
t2 = usec();
|
||||
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);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
replies = hi_malloc_safe(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
|
||||
t1 = usec();
|
||||
@ -799,7 +1164,7 @@ static void test_throughput(struct config config) {
|
||||
}
|
||||
t2 = usec();
|
||||
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);
|
||||
|
||||
disconnect(c, 0);
|
||||
@ -916,9 +1281,8 @@ int main(int argc, char **argv) {
|
||||
};
|
||||
int throughput = 1;
|
||||
int test_inherit_fd = 1;
|
||||
|
||||
/* Ignore broken pipe signal (for I/O error tests). */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
int skips_as_fails = 0;
|
||||
int test_unix_socket;
|
||||
|
||||
/* Parse command line options. */
|
||||
argv++; argc--;
|
||||
@ -936,6 +1300,8 @@ int main(int argc, char **argv) {
|
||||
throughput = 0;
|
||||
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
||||
test_inherit_fd = 0;
|
||||
} else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) {
|
||||
skips_as_fails = 1;
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
|
||||
argv++; argc--;
|
||||
@ -960,6 +1326,19 @@ int main(int argc, char **argv) {
|
||||
argv++; argc--;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
/* Ignore broken pipe signal (for I/O error tests). */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0;
|
||||
|
||||
#else
|
||||
/* Unix sockets don't exist in Windows */
|
||||
test_unix_socket = 0;
|
||||
#endif
|
||||
|
||||
test_allocator_injection();
|
||||
|
||||
test_format_commands();
|
||||
test_reply_reader();
|
||||
test_blocking_connection_errors();
|
||||
@ -974,15 +1353,25 @@ int main(int argc, char **argv) {
|
||||
test_append_formatted_commands(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
|
||||
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path);
|
||||
cfg.type = CONN_UNIX;
|
||||
test_blocking_connection(cfg);
|
||||
test_blocking_connection_timeouts(cfg);
|
||||
test_blocking_io_errors(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
|
||||
if (test_unix_socket) {
|
||||
printf("\n");
|
||||
cfg.type = CONN_UNIX;
|
||||
test_blocking_connection(cfg);
|
||||
test_blocking_connection_timeouts(cfg);
|
||||
test_blocking_io_errors(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
} else {
|
||||
test_skipped();
|
||||
}
|
||||
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
if (cfg.ssl.port && cfg.ssl.host) {
|
||||
|
||||
redisInitOpenSSL();
|
||||
_ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL);
|
||||
assert(_ssl_ctx != NULL);
|
||||
|
||||
printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
|
||||
cfg.type = CONN_SSL;
|
||||
|
||||
@ -992,21 +1381,31 @@ int main(int argc, char **argv) {
|
||||
test_invalid_timeout_errors(cfg);
|
||||
test_append_formatted_commands(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
|
||||
redisFreeSSLContext(_ssl_ctx);
|
||||
_ssl_ctx = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (test_inherit_fd) {
|
||||
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
|
||||
cfg.type = CONN_FD;
|
||||
test_blocking_connection(cfg);
|
||||
printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
|
||||
if (test_unix_socket) {
|
||||
printf("\n");
|
||||
cfg.type = CONN_FD;
|
||||
test_blocking_connection(cfg);
|
||||
} else {
|
||||
test_skipped();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (fails) {
|
||||
if (fails || (skips_as_fails && skips)) {
|
||||
printf("*** %d TESTS FAILED ***\n", fails);
|
||||
if (skips) {
|
||||
printf("*** %d TESTS SKIPPED ***\n", skips);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("ALL TESTS PASSED\n");
|
||||
printf("ALL TESTS PASSED (%d skipped)\n", skips);
|
||||
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_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
||||
TEST_SSL=${TEST_SSL:-0}
|
||||
SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0}
|
||||
SSL_TEST_ARGS=
|
||||
SKIPS_ARG=
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
||||
@ -67,4 +69,10 @@ fi
|
||||
cat ${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 $@'
|
||||
TEST_LD_MODE=
|
||||
EXTRA_LDFLAGS=
|
||||
ARFLAGS='crus'
|
||||
ARFLAGS='crs'
|
||||
AROUT=' $@'
|
||||
CC_MM=1
|
||||
|
||||
|
2
deps/lua/src/lauxlib.c
vendored
2
deps/lua/src/lauxlib.c
vendored
@ -575,7 +575,7 @@ LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) {
|
||||
if (lf.f == NULL) return errfile(L, "reopen", fnameindex);
|
||||
/* skip eventual `#!...' */
|
||||
while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ;
|
||||
lf.extraline = 0;
|
||||
lf.extraline = 0;
|
||||
}
|
||||
ungetc(c, lf.f);
|
||||
status = lua_load(L, getF, &lf, lua_tostring(L, -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;
|
||||
Closure *cl;
|
||||
struct SParser *p = cast(struct SParser *, ud);
|
||||
int c = luaZ_lookahead(p->z);
|
||||
luaZ_lookahead(p->z);
|
||||
luaC_checkGC(L);
|
||||
tf = (luaY_parser)(L, p->z,
|
||||
&p->buff, p->name);
|
||||
|
2
deps/lua/src/ltablib.c
vendored
2
deps/lua/src/ltablib.c
vendored
@ -137,7 +137,7 @@ static void addfield (lua_State *L, luaL_Buffer *b, int i) {
|
||||
if (!lua_isstring(L, -1))
|
||||
luaL_error(L, "invalid value (%s) at index %d in table for "
|
||||
LUA_QL("concat"), luaL_typename(L, -1), i);
|
||||
luaL_addvalue(b);
|
||||
luaL_addvalue(b);
|
||||
}
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
2
runtest
2
runtest
@ -2,6 +2,8 @@
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
export ASAN_OPTIONS=allocator_may_return_null=1 $ASAN_OPTIONS
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
@ -8,7 +8,7 @@ done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
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
|
||||
fi
|
||||
$TCLSH tests/cluster/run.tcl $*
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
[ -z "$MAKE" ] && MAKE=make
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
@ -8,11 +9,11 @@ done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
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
|
||||
fi
|
||||
|
||||
make -C tests/modules && \
|
||||
$MAKE -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/commandfilter \
|
||||
--single unit/moduleapi/fork \
|
||||
@ -22,6 +23,7 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/hooks \
|
||||
--single unit/moduleapi/misc \
|
||||
--single unit/moduleapi/blockonkeys \
|
||||
--single unit/moduleapi/blockonbackground \
|
||||
--single unit/moduleapi/scan \
|
||||
--single unit/moduleapi/datatype \
|
||||
--single unit/moduleapi/auth \
|
||||
@ -30,5 +32,10 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/moduleloadsave \
|
||||
--single unit/moduleapi/getkeys \
|
||||
--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 \
|
||||
"${@}"
|
||||
|
@ -124,6 +124,42 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
# Default is 30 seconds.
|
||||
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>
|
||||
#
|
||||
# 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
|
||||
# group with the same "requirepass" password. Check the following documentation
|
||||
# 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>
|
||||
#
|
||||
@ -262,3 +321,21 @@ sentinel deny-scripts-reconfig yes
|
||||
# is possible to just rename a command to itself:
|
||||
#
|
||||
# SENTINEL rename-command mymaster CONFIG CONFIG
|
||||
|
||||
# HOSTNAMES SUPPORT
|
||||
#
|
||||
# Normally Sentinel uses only IP addresses and requires SENTINEL MONITOR
|
||||
# to specify an IP address. Also, it requires the Redis replica-announce-ip
|
||||
# keyword to specify only IP addresses.
|
||||
#
|
||||
# You may enable hostnames support by enabling resolve-hostnames. Note
|
||||
# that you must make sure your DNS is configured properly and that DNS
|
||||
# resolution does not introduce very long delays.
|
||||
#
|
||||
SENTINEL resolve-hostnames no
|
||||
|
||||
# When resolve-hostnames is enabled, Sentinel still uses IP addresses
|
||||
# when exposing instances to users, configuration files, etc. If you want
|
||||
# to retain the hostnames when announced, enable announce-hostnames below.
|
||||
#
|
||||
SENTINEL announce-hostnames no
|
||||
|
82
src/Makefile
82
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_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
STD=-std=c11 -pedantic -DREDIS_STATIC=''
|
||||
STD=-pedantic -DREDIS_STATIC=''
|
||||
CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS
|
||||
ifneq (,$(findstring clang,$(CC)))
|
||||
STD+=-Wno-c11-extensions
|
||||
else
|
||||
ifneq (,$(findstring FreeBSD,$(uname_S)))
|
||||
STD+=-Wno-c11-extensions
|
||||
endif
|
||||
@ -30,6 +32,16 @@ endif
|
||||
WARN=-Wall -W -Wno-missing-field-initializers
|
||||
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
|
||||
INSTALL_BIN=$(PREFIX)/bin
|
||||
INSTALL=install
|
||||
@ -117,10 +129,12 @@ endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
ifneq ($(@@),32bit)
|
||||
CFLAGS+= -m64
|
||||
ifeq ($(findstring -m32,$(FINAL_CFLAGS)),)
|
||||
CFLAGS+=-m64
|
||||
CXXFLAGS+= -m64
|
||||
LDFLAGS+= -m64
|
||||
endif
|
||||
ifeq ($(findstring -m32,$(FINAL_LDFLAGS)),)
|
||||
LDFLAGS+=-m64
|
||||
endif
|
||||
DEBUG=-g
|
||||
DEBUG_FLAGS=-g
|
||||
@ -133,8 +147,18 @@ else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin
|
||||
FINAL_LIBS+= -ldl
|
||||
# Homebrew's OpenSSL is not linked to /usr/local to avoid
|
||||
# conflicts with the system's LibreSSL installation so it
|
||||
# must be referenced explicitly during build.
|
||||
ifeq ($(uname_M),arm64)
|
||||
# Homebrew arm64 uses /opt/homebrew as HOMEBREW_PREFIX
|
||||
OPENSSL_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_LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||
endif
|
||||
else
|
||||
ifeq ($(uname_S),AIX)
|
||||
# AIX
|
||||
@ -202,11 +226,13 @@ endif
|
||||
endif
|
||||
endif
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
FINAL_CXXFLAGS+= -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 -I../deps/hdr_histogram
|
||||
|
||||
# Determine systemd support and/or build preference (defaulting to auto-detection)
|
||||
BUILD_WITH_SYSTEMD=no
|
||||
LIBSYSTEMD_LIBS=-lsystemd
|
||||
|
||||
# If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to
|
||||
# auto-detect libsystemd's presence and link accordingly.
|
||||
ifneq ($(USE_SYSTEMD),no)
|
||||
@ -215,17 +241,18 @@ ifneq ($(USE_SYSTEMD),no)
|
||||
# (unless a later check tells us otherwise)
|
||||
ifeq ($(LIBSYSTEMD_PKGCONFIG),0)
|
||||
BUILD_WITH_SYSTEMD=yes
|
||||
LIBSYSTEMD_LIBS=$(shell $(PKG_CONFIG) --libs libsystemd)
|
||||
endif
|
||||
endif
|
||||
|
||||
# If 'USE_SYSTEMD' is set to "yes" use pkg-config if available or fall back to
|
||||
# default -lsystemd.
|
||||
ifeq ($(USE_SYSTEMD),yes)
|
||||
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
|
||||
endif
|
||||
|
||||
ifeq ($(BUILD_WITH_SYSTEMD),yes)
|
||||
FINAL_LIBS+=$(shell $(PKG_CONFIG) --libs libsystemd)
|
||||
FINAL_LIBS+=$(LIBSYSTEMD_LIBS)
|
||||
FINAL_CFLAGS+= -DHAVE_LIBSYSTEMD
|
||||
endif
|
||||
|
||||
@ -256,10 +283,9 @@ ifeq ($(MALLOC),memkind)
|
||||
endif
|
||||
|
||||
ifeq ($(BUILD_TLS),yes)
|
||||
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
|
||||
FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
|
||||
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
||||
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
|
||||
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
|
||||
FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
|
||||
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
||||
LIBSSL_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libssl && echo $$?)
|
||||
ifeq ($(LIBSSL_PKGCONFIG),0)
|
||||
LIBSSL_LIBS=$(shell $(PKG_CONFIG) --libs libssl)
|
||||
@ -296,11 +322,11 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=keydb-server$(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_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_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_AOF_NAME=keydb-check-aof$(PROG_SUFFIX)
|
||||
|
||||
@ -374,9 +400,9 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
|
||||
# keydb-benchmark
|
||||
$(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)
|
||||
|
||||
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
||||
@ -407,10 +433,10 @@ distclean: clean
|
||||
|
||||
.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)
|
||||
|
||||
test-sentinel: $(REDIS_SENTINEL_NAME)
|
||||
test-sentinel: $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME)
|
||||
@(cd ..; ./runtest-sentinel)
|
||||
|
||||
check: test
|
||||
@ -422,10 +448,6 @@ lcov:
|
||||
@genhtml --legend -o lcov-html KeyDB.info
|
||||
@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
|
||||
|
||||
bench: $(REDIS_BENCHMARK_NAME)
|
||||
@ -447,7 +469,7 @@ valgrind:
|
||||
$(MAKE) OPTIMIZATION="-O0" USEASM="false" MALLOC="libc" CFLAGS="-DSANITIZE" CXXFLAGS="-DSANITIZE"
|
||||
|
||||
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:
|
||||
@../utils/generate-command-help.rb > help.h
|
||||
@ -457,8 +479,8 @@ install: all
|
||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||
|
||||
uninstall:
|
||||
|
377
src/acl.cpp
377
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
|
||||
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 {
|
||||
const char *name;
|
||||
uint64_t flag;
|
||||
@ -88,18 +92,22 @@ struct ACLUserFlag {
|
||||
const char *name;
|
||||
uint64_t flag;
|
||||
} ACLUserFlags[] = {
|
||||
/* Note: the order here dictates the emitted order at ACLDescribeUser */
|
||||
{"on", USER_FLAG_ENABLED},
|
||||
{"off", USER_FLAG_DISABLED},
|
||||
{"allkeys", USER_FLAG_ALLKEYS},
|
||||
{"allchannels", USER_FLAG_ALLCHANNELS},
|
||||
{"allcommands", USER_FLAG_ALLCOMMANDS},
|
||||
{"nopass", USER_FLAG_NOPASS},
|
||||
{"skip-sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD_SKIP},
|
||||
{"sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD},
|
||||
{NULL,0} /* Terminator. */
|
||||
};
|
||||
|
||||
void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
||||
void ACLResetSubcommands(user *u);
|
||||
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. */
|
||||
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
|
||||
@ -169,15 +177,15 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||
return sdsnewlen(hex,HASH_PASSWORD_LEN);
|
||||
}
|
||||
|
||||
/* Given a hash and the hash length, returns C_OK if it is a valid password
|
||||
/* Given a hash and the hash length, returns C_OK if it is a valid password
|
||||
* hash, or C_ERR otherwise. */
|
||||
int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
|
||||
if (hashlen != HASH_PASSWORD_LEN) {
|
||||
return C_ERR;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
|
||||
/* Password hashes can only be characters that represent
|
||||
* hexadecimal values, which are numbers and lowercase
|
||||
* hexadecimal values, which are numbers and lowercase
|
||||
* characters 'a' through 'f'. */
|
||||
for(int i = 0; i < HASH_PASSWORD_LEN; i++) {
|
||||
char c = hash[i];
|
||||
@ -240,16 +248,20 @@ user *ACLCreateUser(const char *name, size_t namelen) {
|
||||
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
|
||||
user *u = (user*)zmalloc(sizeof(*u), MALLOC_LOCAL);
|
||||
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->passwords = listCreate();
|
||||
u->patterns = listCreate();
|
||||
u->channels = listCreate();
|
||||
listSetMatchMethod(u->passwords,ACLListMatchSds);
|
||||
listSetFreeMethod(u->passwords,ACLListFreeSds);
|
||||
listSetDupMethod(u->passwords,ACLListDupSds);
|
||||
listSetMatchMethod(u->patterns,ACLListMatchSds);
|
||||
listSetFreeMethod(u->patterns,ACLListFreeSds);
|
||||
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));
|
||||
raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
|
||||
return u;
|
||||
@ -278,6 +290,7 @@ void ACLFreeUser(user *u) {
|
||||
sdsfree(u->name);
|
||||
listRelease(u->passwords);
|
||||
listRelease(u->patterns);
|
||||
listRelease(u->channels);
|
||||
ACLResetSubcommands(u);
|
||||
zfree(u);
|
||||
}
|
||||
@ -291,14 +304,14 @@ void ACLFreeUserAndKillClients(user *u) {
|
||||
listRewind(g_pserver->clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = (client*)listNodeValue(ln);
|
||||
if (c->puser == u) {
|
||||
/* We'll free the conenction asynchronously, so
|
||||
if (c->user == u) {
|
||||
/* We'll free the connection asynchronously, so
|
||||
* in theory to set a different user is not needed.
|
||||
* However if there are bugs in Redis, soon or later
|
||||
* this may result in some security hole: it's much
|
||||
* more defensive to set the default user and put
|
||||
* it in non authenticated mode. */
|
||||
c->puser = DefaultUser;
|
||||
c->user = DefaultUser;
|
||||
c->authenticated = 0;
|
||||
/* We will write replies to this client later, so we can't
|
||||
* close it directly even if async. */
|
||||
@ -318,8 +331,10 @@ void ACLFreeUserAndKillClients(user *u) {
|
||||
void ACLCopyUser(user *dst, user *src) {
|
||||
listRelease(dst->passwords);
|
||||
listRelease(dst->patterns);
|
||||
listRelease(dst->channels);
|
||||
dst->passwords = listDup(src->passwords);
|
||||
dst->patterns = listDup(src->patterns);
|
||||
dst->channels = listDup(src->channels);
|
||||
memcpy(dst->allowed_commands,src->allowed_commands,
|
||||
sizeof(dst->allowed_commands));
|
||||
dst->flags = src->flags;
|
||||
@ -601,9 +616,10 @@ sds ACLDescribeUser(user *u) {
|
||||
|
||||
/* Flags. */
|
||||
for (int j = 0; ACLUserFlags[j].flag; j++) {
|
||||
/* Skip the allcommands and allkeys flags because they'll be emitted
|
||||
* later as ~* and +@all. */
|
||||
/* Skip the allcommands, allkeys and allchannels flags because they'll
|
||||
* be emitted later as +@all, ~* and &*. */
|
||||
if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS ||
|
||||
ACLUserFlags[j].flag == USER_FLAG_ALLCHANNELS ||
|
||||
ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue;
|
||||
if (u->flags & ACLUserFlags[j].flag) {
|
||||
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. */
|
||||
sds rules = ACLDescribeUserCommandRules(u);
|
||||
res = sdscatsds(res,rules);
|
||||
@ -680,7 +709,6 @@ void ACLResetSubcommands(user *u) {
|
||||
u->allowed_subcommands = NULL;
|
||||
}
|
||||
|
||||
|
||||
/* Add a subcommand to the list of subcommands for the user 'u' and
|
||||
* the command id specified. */
|
||||
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.
|
||||
* allkeys Alias for ~*
|
||||
* 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.
|
||||
* For example >mypass will add "mypass" to the list.
|
||||
* 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:
|
||||
*
|
||||
* 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).
|
||||
* ENOENT: The command name or command category provided with + or - is not
|
||||
* 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
|
||||
* almost surely an error on the user side.
|
||||
* EISDIR: You are adding a channel pattern after "*" was already added. This is
|
||||
* almost surely an error on the user side.
|
||||
* ENODEV: The password you are trying to remove from the user does not exist.
|
||||
* EBADMSG: The hash you are trying to add is not a valid hash.
|
||||
*/
|
||||
@ -799,6 +833,12 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
} else if (!strcasecmp(op,"off")) {
|
||||
u->flags |= USER_FLAG_DISABLED;
|
||||
u->flags &= ~USER_FLAG_ENABLED;
|
||||
} else if (!strcasecmp(op,"skip-sanitize-payload")) {
|
||||
u->flags |= USER_FLAG_SANITIZE_PAYLOAD_SKIP;
|
||||
u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD;
|
||||
} else if (!strcasecmp(op,"sanitize-payload")) {
|
||||
u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD_SKIP;
|
||||
u->flags |= USER_FLAG_SANITIZE_PAYLOAD;
|
||||
} else if (!strcasecmp(op,"allkeys") ||
|
||||
!strcasecmp(op,"~*"))
|
||||
{
|
||||
@ -807,6 +847,14 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
} else if (!strcasecmp(op,"resetkeys")) {
|
||||
u->flags &= ~USER_FLAG_ALLKEYS;
|
||||
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") ||
|
||||
!strcasecmp(op,"+@all"))
|
||||
{
|
||||
@ -874,12 +922,29 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
}
|
||||
sds newpat = sdsnewlen(op+1,oplen-1);
|
||||
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)
|
||||
listAddNodeTail(u->patterns,newpat);
|
||||
else
|
||||
sdsfree(newpat);
|
||||
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] != '@') {
|
||||
if (strchr(op,'|') == NULL) {
|
||||
if (ACLLookupCommand(op+1) == NULL) {
|
||||
@ -912,22 +977,12 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
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);
|
||||
if (ACLGetUserCommandBit(u,id) == 1) {
|
||||
zfree(copy);
|
||||
errno = EBUSY;
|
||||
return C_ERR;
|
||||
/* Add the subcommand to the list of valid ones, if the command is not set. */
|
||||
if (ACLGetUserCommandBit(u,id) == 0) {
|
||||
ACLAddAllowedSubcommand(u,id,sub);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
} 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")) {
|
||||
serverAssert(ACLSetUser(u,"resetpass",-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,"sanitize-payload",-1) == C_OK);
|
||||
serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
@ -964,15 +1021,16 @@ const char *ACLSetUserStringError(void) {
|
||||
errmsg = "Unknown command or category name in ACL";
|
||||
else if (errno == EINVAL)
|
||||
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)
|
||||
errmsg = "Adding a pattern after the * pattern (or the "
|
||||
"'allkeys' flag) is not valid and does not have any "
|
||||
"effect. Try 'resetkeys' to start with an empty "
|
||||
"list of patterns";
|
||||
else if (errno == EISDIR)
|
||||
errmsg = "Adding a pattern after the * pattern (or the "
|
||||
"'allchannels' flag) is not valid and does not have any "
|
||||
"effect. Try 'resetchannels' to start with an empty "
|
||||
"list of channels";
|
||||
else if (errno == ENODEV)
|
||||
errmsg = "The password you are trying to remove from the user does "
|
||||
"not exist";
|
||||
@ -988,6 +1046,7 @@ void ACLInitDefaultUser(void) {
|
||||
DefaultUser = ACLCreateUser("default",7);
|
||||
ACLSetUser(DefaultUser,"+@all",-1);
|
||||
ACLSetUser(DefaultUser,"~*",-1);
|
||||
ACLSetUser(DefaultUser,"&*",-1);
|
||||
ACLSetUser(DefaultUser,"on",-1);
|
||||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
}
|
||||
@ -998,7 +1057,6 @@ void ACLInit(void) {
|
||||
UsersToLoad = listCreate();
|
||||
ACLLog = listCreate();
|
||||
ACLInitDefaultUser();
|
||||
g_pserver->requirepass = NULL; /* Only used for backward compatibility. */
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
if (ACLCheckUserCredentials(username,password) == C_OK) {
|
||||
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);
|
||||
return C_OK;
|
||||
} 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
|
||||
* are unloaded and later reloaded. */
|
||||
unsigned long ACLGetCommandID(const char *cmdname) {
|
||||
static rax *map = NULL;
|
||||
static unsigned long nextid = 0;
|
||||
|
||||
sds lowername = sdsnew(cmdname);
|
||||
sdstolower(lowername);
|
||||
if (map == NULL) map = raxNew();
|
||||
void *id = raxFind(map,(unsigned char*)lowername,sdslen(lowername));
|
||||
if (commandId == NULL) commandId = raxNew();
|
||||
void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername));
|
||||
if (id != raxNotFound) {
|
||||
sdsfree(lowername);
|
||||
return (unsigned long)id;
|
||||
}
|
||||
raxInsert(map,(unsigned char*)lowername,strlen(lowername),
|
||||
raxInsert(commandId,(unsigned char*)lowername,strlen(lowername),
|
||||
(void*)nextid,NULL);
|
||||
sdsfree(lowername);
|
||||
unsigned long thisid = nextid;
|
||||
@ -1097,6 +1153,13 @@ unsigned long ACLGetCommandID(const char *cmdname) {
|
||||
return thisid;
|
||||
}
|
||||
|
||||
/* Clear command id table and reset nextid to 0. */
|
||||
void ACLClearCommandID(void) {
|
||||
if (commandId) raxFree(commandId);
|
||||
commandId = NULL;
|
||||
nextid = 0;
|
||||
}
|
||||
|
||||
/* Return an username by its name, or NULL if the user does not exist. */
|
||||
user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
void *myuser = 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
|
||||
* 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
|
||||
* 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
|
||||
* to access keys that are not among the specified patterns. */
|
||||
int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
||||
user *u = c->puser;
|
||||
user *u = c->user;
|
||||
uint64_t id = c->cmd->id;
|
||||
|
||||
/* 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
|
||||
* 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))
|
||||
{
|
||||
getKeysResult result = GETKEYS_RESULT_INIT;
|
||||
@ -1187,6 +1250,119 @@ int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
||||
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
|
||||
* ==========================================================================*/
|
||||
@ -1588,8 +1764,8 @@ int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
|
||||
}
|
||||
|
||||
/* Release an ACL log entry. */
|
||||
void ACLFreeLogEntry(ACLLogEntry *leptr) {
|
||||
ACLLogEntry *le = leptr;
|
||||
void ACLFreeLogEntry(const void *leptr) {
|
||||
ACLLogEntry *le = (ACLLogEntry*)leptr;
|
||||
sdsfree(le->object);
|
||||
sdsfree(le->username);
|
||||
sdsfree(le->cinfo);
|
||||
@ -1602,24 +1778,25 @@ void ACLFreeLogEntry(ACLLogEntry *leptr) {
|
||||
* the log entry instead of creating many entries for very similar ACL
|
||||
* rules issues.
|
||||
*
|
||||
* The keypos argument is only used when the reason is ACL_DENIED_KEY, since
|
||||
* it allows the function to log the key name that caused the problem.
|
||||
* Similarly the username is only passed when we failed to authenticate the
|
||||
* user with AUTH or HELLO, for the ACL_DENIED_AUTH reason. Otherwise
|
||||
* it will just be NULL.
|
||||
* The argpos argument is used when the reason is ACL_DENIED_KEY or
|
||||
* ACL_DENIED_CHANNEL, since it allows the function to log the key or channel
|
||||
* name that caused the problem. Similarly the username is only passed when we
|
||||
* failed to authenticate the user with AUTH or HELLO, for the ACL_DENIED_AUTH
|
||||
* 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. */
|
||||
struct ACLLogEntry *le = (ACLLogEntry*)zmalloc(sizeof(*le));
|
||||
le->count = 1;
|
||||
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();
|
||||
|
||||
switch(reason) {
|
||||
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_AUTH: le->object = sdsnew(szFromObj(c->argv[0])); break;
|
||||
case ACL_DENIED_KEY: le->object = sdsdup(szFromObj(c->argv[argpos])); 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();
|
||||
}
|
||||
|
||||
@ -1666,7 +1843,7 @@ void addACLLogEntry(client *c, int reason, int keypos, sds username) {
|
||||
le->cinfo = NULL;
|
||||
ACLFreeLogEntry(le);
|
||||
} 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. */
|
||||
listAddNodeHead(ACLLog, le);
|
||||
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. */
|
||||
if (!u) u = ACLCreateUser(username,sdslen(username));
|
||||
serverAssert(u != NULL);
|
||||
@ -1762,7 +1944,7 @@ void aclCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyMapLen(c,4);
|
||||
addReplyMapLen(c,5);
|
||||
|
||||
/* Flags */
|
||||
addReplyBulkCString(c,"flags");
|
||||
@ -1807,6 +1989,22 @@ void aclCommand(client *c) {
|
||||
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")) &&
|
||||
c->argc == 2)
|
||||
{
|
||||
@ -1832,8 +2030,8 @@ void aclCommand(client *c) {
|
||||
}
|
||||
raxStop(&ri);
|
||||
} else if (!strcasecmp(sub,"whoami") && c->argc == 2) {
|
||||
if (c->puser != NULL) {
|
||||
addReplyBulkCBuffer(c,c->puser->name,sdslen(c->puser->name));
|
||||
if (c->user != NULL) {
|
||||
addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
}
|
||||
@ -1911,7 +2109,7 @@ void aclCommand(client *c) {
|
||||
* the "RESET" command in order to flush the old entries. */
|
||||
if (c->argc == 3) {
|
||||
if (!strcasecmp(szFromObj(c->argv[2]),"reset")) {
|
||||
listSetFreeMethod(ACLLog,(void(*)(const void*))ACLFreeLogEntry);
|
||||
listSetFreeMethod(ACLLog,ACLFreeLogEntry);
|
||||
listEmpty(ACLLog);
|
||||
listSetFreeMethod(ACLLog,NULL);
|
||||
addReply(c,shared.ok);
|
||||
@ -1940,10 +2138,11 @@ void aclCommand(client *c) {
|
||||
addReplyLongLong(c,le->count);
|
||||
|
||||
addReplyBulkCString(c,"reason");
|
||||
const char *reasonstr = "INVALID_REASON";
|
||||
const char *reasonstr;
|
||||
switch(le->reason) {
|
||||
case ACL_DENIED_CMD: reasonstr="command"; break;
|
||||
case ACL_DENIED_KEY: reasonstr="key"; break;
|
||||
case ACL_DENIED_CHANNEL: reasonstr="channel"; break;
|
||||
case ACL_DENIED_AUTH: reasonstr="auth"; break;
|
||||
default: reasonstr="unknown";
|
||||
}
|
||||
@ -1971,18 +2170,30 @@ void aclCommand(client *c) {
|
||||
}
|
||||
} else if (c->argc == 2 && !strcasecmp(sub,"help")) {
|
||||
const char *help[] = {
|
||||
"LOAD -- Reload users from the ACL file.",
|
||||
"SAVE -- Save the current config to the ACL file.",
|
||||
"LIST -- Show user details in config file format.",
|
||||
"USERS -- List all the registered usernames.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"DELUSER <username> [...] -- Delete a list of users.",
|
||||
"CAT -- List available categories.",
|
||||
"CAT <category> -- List commands inside category.",
|
||||
"GENPASS [<bits>] -- Generate a secure user password.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
||||
"CAT [<category>]",
|
||||
" List all commands that belong to <category>, or all command categories",
|
||||
" when no category is specified.",
|
||||
"DELUSER <username> [<username> ...]",
|
||||
" Delete a list of users.",
|
||||
"GETUSER <username>",
|
||||
" Get the user's details.",
|
||||
"GENPASS [<bits>]",
|
||||
" Generate a secure 256-bit user password. The optional `bits` argument can",
|
||||
" be used to specify a different size.",
|
||||
"LIST",
|
||||
" Show users details in config file format.",
|
||||
"LOAD",
|
||||
" Reload users from the ACL file.",
|
||||
"LOG [<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
|
||||
};
|
||||
addReplyHelp(c,help);
|
||||
@ -2011,7 +2222,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
|
||||
void authCommand(client *c) {
|
||||
/* Only two or three argument forms are allowed. */
|
||||
if (c->argc > 3) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2028,7 +2239,7 @@ void authCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
username = createStringObject("default",7);
|
||||
username = shared.default_username;
|
||||
password = c->argv[1];
|
||||
} else {
|
||||
username = c->argv[1];
|
||||
@ -2038,11 +2249,19 @@ void authCommand(client *c) {
|
||||
if (ACLAuthenticateUser(c,username,password) == C_OK) {
|
||||
addReply(c,shared.ok);
|
||||
} 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
|
||||
* arguments form. */
|
||||
if (c->argc == 2) decrRefCount(username);
|
||||
}
|
||||
|
||||
/* Set the password for the "default" ACL user. This implements supports for
|
||||
* requirepass config, so passing in NULL will set the user to be nopass. */
|
||||
void ACLUpdateDefaultUserPassword(sds password) {
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
if (password) {
|
||||
sds aclop = sdscatlen(sdsnew(">"), password, sdslen(password));
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
} else {
|
||||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
}
|
||||
}
|
||||
|
@ -360,15 +360,16 @@ void listRotateHeadToTail(list *list) {
|
||||
/* Add all the elements of the list 'o' at the end of the
|
||||
* list 'l'. The list 'other' remains empty but otherwise valid. */
|
||||
void listJoin(list *l, list *o) {
|
||||
if (o->head)
|
||||
o->head->prev = l->tail;
|
||||
if (o->len == 0) return;
|
||||
|
||||
o->head->prev = l->tail;
|
||||
|
||||
if (l->tail)
|
||||
l->tail->next = o->head;
|
||||
else
|
||||
l->head = o->head;
|
||||
|
||||
if (o->tail) l->tail = o->tail;
|
||||
l->tail = o->tail;
|
||||
l->len += o->len;
|
||||
|
||||
/* Setup other as an empty list. */
|
||||
|
122
src/ae.cpp
122
src/ae.cpp
@ -30,6 +30,10 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ae.h"
|
||||
#include "anet.h"
|
||||
#include "fastlock.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
@ -271,12 +275,13 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
aeEventLoop *eventLoop;
|
||||
int i;
|
||||
|
||||
monotonicInit(); /* just in case the calling app didn't initialize */
|
||||
|
||||
if ((eventLoop = (aeEventLoop*)zmalloc(sizeof(*eventLoop), MALLOC_LOCAL)) == NULL) goto err;
|
||||
eventLoop->events = (aeFileEvent*)zmalloc(sizeof(aeFileEvent)*setsize, MALLOC_LOCAL);
|
||||
eventLoop->fired = (aeFiredEvent*)zmalloc(sizeof(aeFiredEvent)*setsize, MALLOC_LOCAL);
|
||||
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
|
||||
eventLoop->setsize = setsize;
|
||||
eventLoop->lastTime = time(NULL);
|
||||
eventLoop->timeEventHead = NULL;
|
||||
eventLoop->timeEventNextId = 0;
|
||||
eventLoop->stop = 0;
|
||||
@ -448,29 +453,6 @@ extern "C" int aeGetFileEvents(aeEventLoop *eventLoop, int fd) {
|
||||
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,
|
||||
aeTimeProc *proc, void *clientData,
|
||||
aeEventFinalizerProc *finalizerProc)
|
||||
@ -482,7 +464,7 @@ extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long millise
|
||||
te = (aeTimeEvent*)zmalloc(sizeof(*te), MALLOC_LOCAL);
|
||||
if (te == NULL) return AE_ERR;
|
||||
te->id = id;
|
||||
aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
|
||||
te->when = getMonotonicUs() + milliseconds * 1000;
|
||||
te->timeProc = proc;
|
||||
te->finalizerProc = finalizerProc;
|
||||
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 */
|
||||
}
|
||||
|
||||
/* Search the first timer to fire.
|
||||
* This operation is useful to know how many time the select can be
|
||||
* put in sleep without to delay any event.
|
||||
* If there are no timers NULL is returned.
|
||||
/* How many milliseconds until the first timer should fire.
|
||||
* If there are no timers, -1 is returned.
|
||||
*
|
||||
* Note that's O(N) since time events are unsorted.
|
||||
* Possible optimizations (not needed by Redis so far, but...):
|
||||
@ -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).
|
||||
* 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
|
||||
*/
|
||||
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
|
||||
{
|
||||
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
|
||||
static long msUntilEarliestTimer(aeEventLoop *eventLoop) {
|
||||
aeTimeEvent *te = eventLoop->timeEventHead;
|
||||
aeTimeEvent *nearest = NULL;
|
||||
if (te == NULL) return -1;
|
||||
|
||||
while(te) {
|
||||
if (!nearest || te->when_sec < nearest->when_sec ||
|
||||
(te->when_sec == nearest->when_sec &&
|
||||
te->when_ms < nearest->when_ms))
|
||||
nearest = te;
|
||||
aeTimeEvent *earliest = NULL;
|
||||
while (te) {
|
||||
if (!earliest || te->when < earliest->when)
|
||||
earliest = te;
|
||||
te = te->next;
|
||||
}
|
||||
return nearest;
|
||||
|
||||
monotime now = getMonotonicUs();
|
||||
return (now >= earliest->when)
|
||||
? 0 : (long)((earliest->when - now) / 1000);
|
||||
}
|
||||
|
||||
/* Process time events */
|
||||
@ -542,29 +522,11 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
int processed = 0;
|
||||
aeTimeEvent *te;
|
||||
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;
|
||||
maxId = eventLoop->timeEventNextId-1;
|
||||
monotime now = getMonotonicUs();
|
||||
while(te) {
|
||||
long now_sec, now_ms;
|
||||
long long id;
|
||||
|
||||
/* Remove events scheduled for deletion. */
|
||||
@ -586,6 +548,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
if (te->finalizerProc) {
|
||||
if (!ulock.owns_lock()) ulock.lock();
|
||||
te->finalizerProc(eventLoop, te->clientData);
|
||||
now = getMonotonicUs();
|
||||
}
|
||||
zfree(te);
|
||||
te = next;
|
||||
@ -601,10 +564,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
te = te->next;
|
||||
continue;
|
||||
}
|
||||
aeGetTime(&now_sec, &now_ms);
|
||||
if (now_sec > te->when_sec ||
|
||||
(now_sec == te->when_sec && now_ms >= te->when_ms))
|
||||
{
|
||||
|
||||
if (te->when <= now) {
|
||||
if (!ulock.owns_lock()) ulock.lock();
|
||||
int retval;
|
||||
|
||||
@ -613,8 +574,9 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
retval = te->timeProc(eventLoop, id, te->clientData);
|
||||
te->refcount--;
|
||||
processed++;
|
||||
now = getMonotonicUs();
|
||||
if (retval != AE_NOMORE) {
|
||||
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
|
||||
te->when = now + retval * 1000;
|
||||
} else {
|
||||
te->id = AE_DELETED_EVENT_ID;
|
||||
}
|
||||
@ -704,37 +666,23 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
/* Nothing to do? return ASAP */
|
||||
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
|
||||
* events, in order to sleep until the next time event is ready
|
||||
* to fire. */
|
||||
if (eventLoop->maxfd != -1 ||
|
||||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
|
||||
int j;
|
||||
aeTimeEvent *shortest = NULL;
|
||||
struct timeval tv, *tvp;
|
||||
long msUntilTimer = -1;
|
||||
|
||||
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
|
||||
shortest = aeSearchNearestTimer(eventLoop);
|
||||
if (shortest) {
|
||||
long now_sec, now_ms;
|
||||
msUntilTimer = msUntilEarliestTimer(eventLoop);
|
||||
|
||||
aeGetTime(&now_sec, &now_ms);
|
||||
if (msUntilTimer >= 0) {
|
||||
tv.tv_sec = msUntilTimer / 1000;
|
||||
tv.tv_usec = (msUntilTimer % 1000) * 1000;
|
||||
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 {
|
||||
/* If we have to check for events but need to return
|
||||
* ASAP because of AE_DONT_WAIT we need to set the timeout
|
||||
@ -816,14 +764,8 @@ void aeMain(aeEventLoop *eventLoop) {
|
||||
eventLoop->stop = 0;
|
||||
g_eventLoopThisThread = eventLoop;
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
6
src/ae.h
6
src/ae.h
@ -36,7 +36,7 @@
|
||||
#ifdef __cplusplus
|
||||
#include <functional>
|
||||
#endif
|
||||
#include <time.h>
|
||||
#include "monotonic.h"
|
||||
#include "fastlock.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@ -91,8 +91,7 @@ typedef struct aeFileEvent {
|
||||
/* Time event structure */
|
||||
typedef struct aeTimeEvent {
|
||||
long long id; /* time event identifier. */
|
||||
long when_sec; /* seconds */
|
||||
long when_ms; /* milliseconds */
|
||||
monotime when;
|
||||
aeTimeProc *timeProc;
|
||||
aeEventFinalizerProc *finalizerProc;
|
||||
void *clientData;
|
||||
@ -113,7 +112,6 @@ typedef struct aeEventLoop {
|
||||
int maxfd; /* highest file descriptor currently registered */
|
||||
int setsize; /* max number of file descriptors tracked */
|
||||
long long timeEventNextId;
|
||||
time_t lastTime; /* Used to detect system clock skew */
|
||||
aeFileEvent *events; /* Registered events */
|
||||
aeFiredEvent *fired; /* Fired events */
|
||||
aeTimeEvent *timeEventHead;
|
||||
|
@ -51,6 +51,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
zfree(state);
|
||||
return -1;
|
||||
}
|
||||
anetCloexec(state->epfd);
|
||||
eventLoop->apidata = state;
|
||||
return 0;
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ static int evport_debug = 0;
|
||||
|
||||
typedef struct aeApiState {
|
||||
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_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */
|
||||
} aeApiState;
|
||||
@ -82,6 +82,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
zfree(state);
|
||||
return -1;
|
||||
}
|
||||
anetCloexec(state->portfd);
|
||||
|
||||
state->npending = 0;
|
||||
|
||||
@ -95,6 +96,8 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
}
|
||||
|
||||
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
(void) eventLoop;
|
||||
(void) setsize;
|
||||
/* Nothing to resize here. */
|
||||
return 0;
|
||||
}
|
||||
@ -107,7 +110,7 @@ static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
}
|
||||
|
||||
static int aeApiLookupPending(aeApiState *state, int fd) {
|
||||
int i;
|
||||
uint_t i;
|
||||
|
||||
for (i = 0; i < state->npending; i++) {
|
||||
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) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
struct timespec timeout, *tsp;
|
||||
int mask, i;
|
||||
uint_t mask, i;
|
||||
uint_t nevents;
|
||||
port_event_t event[MAX_EVENT_BATCHSZ];
|
||||
|
||||
|
@ -53,6 +53,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
zfree(state);
|
||||
return -1;
|
||||
}
|
||||
anetCloexec(state->kqfd);
|
||||
eventLoop->apidata = state;
|
||||
return 0;
|
||||
}
|
||||
|
100
src/anet.c
100
src/anet.c
@ -69,6 +69,11 @@ int anetSetBlock(char *err, int fd, int non_block) {
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
/* Check if this flag has been set or unset, if so,
|
||||
* then there is no need to call fcntl to set/unset it again. */
|
||||
if (!!(flags & O_NONBLOCK) == !!non_block)
|
||||
return ANET_OK;
|
||||
|
||||
if (non_block)
|
||||
flags |= O_NONBLOCK;
|
||||
else
|
||||
@ -89,6 +94,29 @@ int anetBlock(char *err, int fd) {
|
||||
return anetSetBlock(err,fd,0);
|
||||
}
|
||||
|
||||
/* Enable the FD_CLOEXEC on the given fd to avoid fd leaks.
|
||||
* This function should be invoked for fd's on specific places
|
||||
* where fork + execve system calls are called. */
|
||||
int anetCloexec(int fd) {
|
||||
int r;
|
||||
int flags;
|
||||
|
||||
do {
|
||||
r = fcntl(fd, F_GETFD);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
|
||||
if (r == -1 || (r & FD_CLOEXEC))
|
||||
return r;
|
||||
|
||||
flags = r | FD_CLOEXEC;
|
||||
|
||||
do {
|
||||
r = fcntl(fd, F_SETFD, flags);
|
||||
} while (r == -1 && errno == EINTR);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* the probe send time, interval, and count. */
|
||||
@ -207,14 +235,13 @@ int anetRecvTimeout(char *err, int fd, long long ms) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
|
||||
* do the actual work. It resolves the hostname "host" and set the string
|
||||
* representation of the IP address into the buffer pointed by "ipbuf".
|
||||
/* Resolve the hostname "host" and set the string representation of the
|
||||
* IP address into the buffer pointed by "ipbuf".
|
||||
*
|
||||
* If flags is set to ANET_IP_ONLY the function only resolves hostnames
|
||||
* that are actually already IPv4 or IPv6 addresses. This turns the function
|
||||
* into a validating / normalizing function. */
|
||||
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)
|
||||
{
|
||||
struct addrinfo hints, *info;
|
||||
@ -241,14 +268,6 @@ int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
||||
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) {
|
||||
int yes = 1;
|
||||
/* Make sure connection-intensive things like the redis benchmark
|
||||
@ -484,13 +503,12 @@ static int anetV6Only(char *err, int s) {
|
||||
int yes = 1;
|
||||
if (setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,&yes,sizeof(yes)) == -1) {
|
||||
anetSetError(err, "setsockopt: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
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;
|
||||
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_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */
|
||||
if (bindaddr && !strcmp("*", bindaddr))
|
||||
bindaddr = NULL;
|
||||
if (af == AF_INET6 && bindaddr && !strcmp("::*", bindaddr))
|
||||
bindaddr = NULL;
|
||||
|
||||
if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
@ -533,12 +555,12 @@ end:
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@ -607,11 +629,15 @@ int anetUnixAccept(char *err, int s) {
|
||||
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;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) goto error;
|
||||
if (fd_to_str_type == FD_TO_PEER_NAME) {
|
||||
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 (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 (port) *port = ntohs(s->sin6_port);
|
||||
} 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;
|
||||
} else {
|
||||
goto error;
|
||||
@ -651,41 +677,11 @@ int anetFormatAddr(char *buf, size_t buf_len, char *ip, int port) {
|
||||
"[%s]:%d" : "%s:%d", ip, port);
|
||||
}
|
||||
|
||||
/* Like anetFormatAddr() but extract ip and port from the socket's peer. */
|
||||
int anetFormatPeer(int fd, char *buf, size_t buf_len) {
|
||||
/* Like anetFormatAddr() but extract ip and port from the socket's peer/sockname. */
|
||||
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type) {
|
||||
char ip[INET6_ADDRSTRLEN];
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
#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 anetTcpNonBlockConnect(char *err, const char *addr, int port);
|
||||
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 anetUnixNonBlockConnect(char *err, const char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags);
|
||||
int anetTcpServer(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||
int anetTcp6Server(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
int anetNonBlock(char *err, int fd);
|
||||
int anetBlock(char *err, int fd);
|
||||
int anetCloexec(int fd);
|
||||
int anetEnableTcpNoDelay(char *err, int fd);
|
||||
int anetDisableTcpNoDelay(char *err, int fd);
|
||||
int anetTcpKeepAlive(char *err, int fd);
|
||||
int anetSendTimeout(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 anetSockName(int fd, char *ip, size_t ip_len, 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 anetFormatSock(int fd, char *fmt, size_t fmt_len);
|
||||
int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
331
src/aof.cpp
331
src/aof.cpp
@ -221,29 +221,27 @@ int aofFsyncInProgress(void) {
|
||||
/* Starts a background task that performs fsync() against the specified
|
||||
* file descriptor (the one of the AOF file) in another thread. */
|
||||
void aof_background_fsync(int fd) {
|
||||
bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
|
||||
bioCreateFsyncJob(fd);
|
||||
}
|
||||
|
||||
/* Kills an AOFRW child process if exists */
|
||||
void killAppendOnlyChild(void) {
|
||||
int statloc;
|
||||
/* 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. */
|
||||
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
||||
(long) g_pserver->aof_child_pid);
|
||||
if (kill(g_pserver->aof_child_pid,SIGUSR1) != -1) {
|
||||
while(wait3(&statloc,0,NULL) != g_pserver->aof_child_pid);
|
||||
(long) g_pserver->child_pid);
|
||||
if (kill(g_pserver->child_pid,SIGUSR1) != -1) {
|
||||
while(wait3(&statloc,0,NULL) != g_pserver->child_pid);
|
||||
}
|
||||
/* Reset the buffer accumulating changes while the child saves. */
|
||||
aofRewriteBufferReset();
|
||||
aofRemoveTempFile(g_pserver->aof_child_pid);
|
||||
g_pserver->aof_child_pid = -1;
|
||||
aofRemoveTempFile(g_pserver->child_pid);
|
||||
resetChildState();
|
||||
g_pserver->aof_rewrite_time_start = -1;
|
||||
/* Close pipes used for IPC between the two processes. */
|
||||
aofClosePipes();
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
|
||||
/* 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);
|
||||
flushAppendOnlyFile(1);
|
||||
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);
|
||||
|
||||
g_pserver->aof_fd = -1;
|
||||
@ -259,6 +259,8 @@ void stopAppendOnly(void) {
|
||||
g_pserver->aof_state = AOF_OFF;
|
||||
g_pserver->aof_rewrite_scheduled = 0;
|
||||
killAppendOnlyChild();
|
||||
sdsfree(g_pserver->aof_buf);
|
||||
g_pserver->aof_buf = sdsempty();
|
||||
}
|
||||
|
||||
/* Called when the user switches from "appendonly no" to "appendonly yes"
|
||||
@ -280,14 +282,14 @@ int startAppendOnly(void) {
|
||||
strerror(errno));
|
||||
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;
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
||||
} else {
|
||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||
* start a new one: the old one cannot be reused because it is not
|
||||
* accumulating the AOF buffer. */
|
||||
if (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.");
|
||||
killAppendOnlyChild();
|
||||
}
|
||||
@ -302,6 +304,12 @@ int startAppendOnly(void) {
|
||||
g_pserver->aof_state = AOF_WAIT_REWRITE;
|
||||
g_pserver->aof_last_fsync = g_pserver->unixtime;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -471,10 +479,11 @@ void flushAppendOnlyFile(int force) {
|
||||
|
||||
/* Handle the AOF write error. */
|
||||
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
/* We can't recover when the fsync policy is ALWAYS since the
|
||||
* reply for the client is already in the output buffers, and we
|
||||
* have the contract with the user that on acknowledged write data
|
||||
* is synced on disk. */
|
||||
/* We can't recover when the fsync policy is ALWAYS since the reply
|
||||
* for the client is already in the output buffers (both writes and
|
||||
* reads), and the changes to the db can't be rolled back. Since we
|
||||
* have a contract with the user that on acknowledged or observed
|
||||
* writes are is synced on disk, we must exit. */
|
||||
serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
|
||||
exit(1);
|
||||
} else {
|
||||
@ -522,7 +531,14 @@ try_fsync:
|
||||
/* redis_fsync is defined as fdatasync() for Linux in order to avoid
|
||||
* flushing metadata. */
|
||||
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);
|
||||
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
||||
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);
|
||||
|
||||
argv[0] = createStringObject("PEXPIREAT",9);
|
||||
argv[0] = shared.pexpireat;
|
||||
argv[1] = key;
|
||||
argv[2] = createStringObjectFromLongLong(when);
|
||||
buf = catAppendOnlyGenericCommand(buf, 3, argv);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[2]);
|
||||
return buf;
|
||||
}
|
||||
@ -653,43 +668,40 @@ sds catAppendOnlyExpireMemberAtCommand(sds buf, struct redisCommand *cmd, robj *
|
||||
}
|
||||
|
||||
sds catCommandForAofAndActiveReplication(sds buf, struct redisCommand *cmd, robj **argv, int argc)
|
||||
{
|
||||
robj *tmpargv[3];
|
||||
|
||||
{
|
||||
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
|
||||
cmd->proc == expireatCommand) {
|
||||
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
|
||||
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) {
|
||||
int i;
|
||||
robj *exarg = NULL, *pxarg = NULL;
|
||||
for (i = 3; i < argc; i ++) {
|
||||
if (!strcasecmp(szFromObj(argv[i]), "ex")) exarg = argv[i+1];
|
||||
if (!strcasecmp(szFromObj(argv[i]), "px")) pxarg = argv[i+1];
|
||||
robj *pxarg = NULL;
|
||||
/* When SET is used with EX/PX argument setGenericCommand propagates them with PX millisecond argument.
|
||||
* So since the command arguments are re-written there, we can rely here on the index of PX being 3. */
|
||||
if (!strcasecmp(szFromObj(argv[3]), "px")) {
|
||||
pxarg = argv[4];
|
||||
}
|
||||
serverAssert(!(exarg && pxarg));
|
||||
if (exarg || pxarg) {
|
||||
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */
|
||||
buf = catAppendOnlyGenericCommand(buf,3,argv);
|
||||
if (exarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,cserver.expireCommand,argv[1],
|
||||
exarg);
|
||||
if (pxarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,cserver.pexpireCommand,argv[1],
|
||||
pxarg);
|
||||
/* For AOF we convert SET key value relative time in milliseconds to SET key value absolute time in
|
||||
* millisecond. Whenever the condition is true it implies that original SET has been transformed
|
||||
* to SET PX with millisecond time argument so we do not need to worry about unit here.*/
|
||||
if (pxarg) {
|
||||
robj *millisecond = getDecodedObject(pxarg);
|
||||
long long when = strtoll(szFromObj(millisecond),NULL,10);
|
||||
when += mstime();
|
||||
|
||||
decrRefCount(millisecond);
|
||||
|
||||
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 {
|
||||
buf = catAppendOnlyGenericCommand(buf,argc,argv);
|
||||
}
|
||||
} else if (cmd->proc == expireMemberCommand || cmd->proc == expireMemberAtCommand ||
|
||||
cmd->proc == pexpireMemberAtCommand) {
|
||||
} else if (cmd->proc == expireMemberCommand || cmd->proc == expireMemberAtCommand || cmd->proc == pexpireMemberAtCommand) {
|
||||
/* Translate subkey expire commands to PEXPIREMEMBERAT */
|
||||
buf = catAppendOnlyExpireMemberAtCommand(buf, cmd, argv, argc);
|
||||
} 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
|
||||
* in a buffer, so that when the child process will do its work we
|
||||
* 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));
|
||||
|
||||
sdsfree(buf);
|
||||
@ -752,11 +764,24 @@ struct client *createAOFClient(void) {
|
||||
c->querybuf_peak = 0;
|
||||
c->argc = 0;
|
||||
c->argv = NULL;
|
||||
c->original_argc = 0;
|
||||
c->original_argv = NULL;
|
||||
c->argv_len_sum = 0;
|
||||
c->bufpos = 0;
|
||||
c->flags = 0;
|
||||
c->fPendingAsyncWrite = 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;
|
||||
/* We set the fake client as a replica waiting for the synchronization
|
||||
* 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->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
c->sockname = NULL;
|
||||
c->resp = 2;
|
||||
c->puser = NULL;
|
||||
c->user = NULL;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
fastlock_init(&c->lock, "fake client");
|
||||
@ -790,6 +816,7 @@ void freeFakeClient(struct client *c) {
|
||||
listRelease(c->reply);
|
||||
listRelease(c->watched_keys);
|
||||
freeClientMultiState(c);
|
||||
freeClientOriginalArgv(c);
|
||||
fastlock_unlock(&c->lock);
|
||||
fastlock_free(&c->lock);
|
||||
zfree(c);
|
||||
@ -951,7 +978,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
fakeClient->cmd = NULL;
|
||||
if (g_pserver->aof_load_truncated) valid_up_to = ftello(fp);
|
||||
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.
|
||||
@ -1052,15 +1079,25 @@ int rewriteListObject(rio *r, robj *key, robj *o) {
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
|
||||
!rioWriteBulkString(r,"RPUSH",5) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
quicklistReleaseIterator(li);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
items--;
|
||||
@ -1086,11 +1123,14 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"SADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
|
||||
!rioWriteBulkString(r,"SADD",4) ||
|
||||
!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;
|
||||
items--;
|
||||
}
|
||||
@ -1104,11 +1144,18 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"SADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
|
||||
!rioWriteBulkString(r,"SADD",4) ||
|
||||
!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;
|
||||
items--;
|
||||
}
|
||||
@ -1145,15 +1192,18 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"ZADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
|
||||
!rioWriteBulkString(r,"ZADD",4) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (rioWriteBulkDouble(r,score) == 0) return 0;
|
||||
if (!rioWriteBulkDouble(r,score)) return 0;
|
||||
if (vstr != NULL) {
|
||||
if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0;
|
||||
if (!rioWriteBulkString(r,(char*)vstr,vlen)) return 0;
|
||||
} else {
|
||||
if (rioWriteBulkLongLong(r,vll) == 0) return 0;
|
||||
if (!rioWriteBulkLongLong(r,vll)) return 0;
|
||||
}
|
||||
zzlNext(zl,&eptr,&sptr);
|
||||
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) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"ZADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
|
||||
!rioWriteBulkString(r,"ZADD",4) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
dictReleaseIterator(di);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!rioWriteBulkDouble(r,*score) ||
|
||||
!rioWriteBulkString(r,ele,sdslen(ele)))
|
||||
{
|
||||
dictReleaseIterator(di);
|
||||
return 0;
|
||||
}
|
||||
if (rioWriteBulkDouble(r,*score) == 0) return 0;
|
||||
if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0;
|
||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
@ -1226,13 +1284,21 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"HMSET",5) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
|
||||
!rioWriteBulkString(r,"HMSET",5) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
hashTypeReleaseIterator(hi);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) == 0) return 0;
|
||||
if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE) == 0) return 0;
|
||||
if (!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) ||
|
||||
!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE))
|
||||
{
|
||||
hashTypeReleaseIterator(hi);
|
||||
return 0;
|
||||
}
|
||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
@ -1278,6 +1344,20 @@ int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t
|
||||
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.
|
||||
* The function returns 0 on error, 1 on success. */
|
||||
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
|
||||
* have pending entries. Empty consumers have no semantical
|
||||
* value so they are discarded. */
|
||||
* have pending entries. Empty consumers would be generated with
|
||||
* XGROUP CREATECONSUMER. */
|
||||
raxIterator ri_cons;
|
||||
raxStart(&ri_cons,group->consumers);
|
||||
raxSeek(&ri_cons,"^",NULL,0);
|
||||
while(raxNext(&ri_cons)) {
|
||||
streamConsumer *consumer = (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
|
||||
* to emit the XCLAIM protocol. */
|
||||
raxIterator ri_pel;
|
||||
@ -1438,6 +1530,8 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
dictEntry *de;
|
||||
size_t processed = 0;
|
||||
int j;
|
||||
long key_count = 0;
|
||||
long long updated_time = 0;
|
||||
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||
@ -1509,6 +1603,17 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
processed = aof->processed_bytes;
|
||||
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);
|
||||
di = NULL;
|
||||
@ -1535,6 +1640,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
int nodata = 0;
|
||||
mstime_t start = 0;
|
||||
|
||||
{ // BEGIN GOTO SCOPED VARIABLES
|
||||
/* Note that we have to use a different temp name here compared to the
|
||||
* one used by rewriteAppendOnlyFileBackground() function. */
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
|
||||
@ -1603,8 +1709,31 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Concatenating %.2f MB of AOF diff received from parent.",
|
||||
(double) sdslen(g_pserver->aof_child_diff) / (1024*1024));
|
||||
if (rioWrite(&aof,g_pserver->aof_child_diff,sdslen(g_pserver->aof_child_diff)) == 0)
|
||||
goto werr;
|
||||
|
||||
/* 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;
|
||||
|
||||
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 */
|
||||
if (fflush(fp)) goto werr;
|
||||
@ -1623,6 +1752,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
||||
stopSaving(1);
|
||||
return C_OK;
|
||||
} // END GOTO SCOPED VARIABLES
|
||||
|
||||
werr:
|
||||
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 (aofCreatePipes() != C_OK) return C_ERR;
|
||||
openChildInfoPipe();
|
||||
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
|
||||
char tmpfile[256];
|
||||
|
||||
@ -1748,7 +1877,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||
sendChildCOWInfo(CHILD_TYPE_AOF, "AOF rewrite");
|
||||
sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");
|
||||
exitFromChild(0);
|
||||
} else {
|
||||
exitFromChild(1);
|
||||
@ -1756,7 +1885,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
} else {
|
||||
/* Parent */
|
||||
if (childpid == -1) {
|
||||
closeChildInfoPipe();
|
||||
serverLog(LL_WARNING,
|
||||
"Can't rewrite append only file in background: fork: %s",
|
||||
strerror(errno));
|
||||
@ -1764,10 +1892,9 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
return C_ERR;
|
||||
}
|
||||
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_time_start = time(NULL);
|
||||
g_pserver->aof_child_pid = childpid;
|
||||
updateDictResizePolicy();
|
||||
/* We set appendseldb to -1 in order to force the next call to the
|
||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||
@ -1781,7 +1908,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
}
|
||||
|
||||
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");
|
||||
} else if (hasActiveChildProcess()) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
@ -1839,7 +1966,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
* rewritten AOF. */
|
||||
latencyStartMonitor(latency);
|
||||
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);
|
||||
if (newfd == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
@ -1855,6 +1982,20 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
}
|
||||
latencyEndMonitor(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,
|
||||
"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. */
|
||||
oldfd = g_pserver->aof_fd;
|
||||
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 */
|
||||
aofUpdateCurrentSize();
|
||||
g_pserver->aof_rewrite_base_size = 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
|
||||
* the new AOF from the background rewrite buffer. */
|
||||
@ -1945,7 +2083,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
g_pserver->aof_state = AOF_ON;
|
||||
|
||||
/* 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,
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
@ -1967,8 +2105,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
cleanup:
|
||||
aofClosePipes();
|
||||
aofRewriteBufferReset();
|
||||
aofRemoveTempFile(g_pserver->aof_child_pid);
|
||||
g_pserver->aof_child_pid = -1;
|
||||
aofRemoveTempFile(g_pserver->child_pid);
|
||||
g_pserver->aof_rewrite_time_last = time(NULL)-g_pserver->aof_rewrite_time_start;
|
||||
g_pserver->aof_rewrite_time_start = -1;
|
||||
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
|
||||
|
@ -41,7 +41,7 @@ const char *ascii_logo =
|
||||
" | / | \\ | https://docs.keydb.dev \n"
|
||||
" | / | \\ | \n"
|
||||
" | / | \\ | \n"
|
||||
" (+)_ -- -- -- | -- -- -- _(+) \n"
|
||||
" (+)_ -- -- -- | -- -- -- _(+) \n"
|
||||
" --_ | _-- \n"
|
||||
" --_ | _-- \n"
|
||||
" -(+)- %s\n"
|
||||
|
109
src/atomicvar.h
109
src/atomicvar.h
@ -1,5 +1,5 @@
|
||||
/* This file implements atomic counters using __atomic or __sync macros if
|
||||
* available, otherwise synchronizing different threads using a mutex.
|
||||
/* This file implements atomic counters using c11 _Atomic, __atomic or __sync
|
||||
* macros if available, otherwise we will throw an error when compile.
|
||||
*
|
||||
* The exported interface is composed of three macros:
|
||||
*
|
||||
@ -8,16 +8,8 @@
|
||||
* atomicDecr(var,count) -- Decrement the atomic counter
|
||||
* atomicGet(var,dstvar) -- Fetch the atomic counter value
|
||||
* atomicSet(var,value) -- Set the atomic counter value
|
||||
*
|
||||
* The variable 'var' should also have a declared mutex with the same
|
||||
* 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.
|
||||
* atomicGetWithSync(var,value) -- 'atomicGet' with inter-thread synchronization
|
||||
* atomicSetWithSync(var,value) -- 'atomicSet' with inter-thread synchronization
|
||||
*
|
||||
* Never use return value from the macros, instead use the AtomicGetIncr()
|
||||
* if you need to get the current value and increment it atomically, like
|
||||
@ -58,17 +50,64 @@
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include "config.h"
|
||||
|
||||
#ifndef __ATOMIC_VAR_H
|
||||
#define __ATOMIC_VAR_H
|
||||
|
||||
/* Define redisAtomic for atomic variable. */
|
||||
#define redisAtomic
|
||||
|
||||
/* To test Redis with Helgrind (a Valgrind tool) it is useful to define
|
||||
* the following macro, so that __sync macros are used: those can be detected
|
||||
* by Helgrind (even if they are less efficient) so that no false positive
|
||||
* is reported. */
|
||||
// #define __ATOMIC_VAR_FORCE_SYNC_MACROS
|
||||
|
||||
#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. */
|
||||
|
||||
#define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED)
|
||||
@ -80,6 +119,11 @@
|
||||
dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \
|
||||
} while(0)
|
||||
#define atomicSet(var,value) __atomic_store_n(&var,value,__ATOMIC_RELAXED)
|
||||
#define atomicGetWithSync(var,dstvar) do { \
|
||||
dstvar = __atomic_load_n(&var,__ATOMIC_SEQ_CST); \
|
||||
} while(0)
|
||||
#define atomicSetWithSync(var,value) \
|
||||
__atomic_store_n(&var,value,__ATOMIC_SEQ_CST)
|
||||
#define REDIS_ATOMIC_API "atomic-builtin"
|
||||
|
||||
#elif defined(HAVE_ATOMIC)
|
||||
@ -96,38 +140,19 @@
|
||||
#define atomicSet(var,value) do { \
|
||||
while(!__sync_bool_compare_and_swap(&var,var,value)); \
|
||||
} while(0)
|
||||
/* Actually the builtin issues a full memory barrier by default. */
|
||||
#define atomicGetWithSync(var,dstvar) { \
|
||||
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"
|
||||
|
||||
#else
|
||||
/* Implementation using pthread mutex. */
|
||||
|
||||
#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"
|
||||
#error "Unable to determine atomic operations for your platform"
|
||||
|
||||
#endif
|
||||
#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. */
|
||||
struct bio_job {
|
||||
time_t time; /* Time at which the job was created. */
|
||||
/* Job specific arguments pointers. If we need to pass more than three
|
||||
* arguments we can just pass a pointer to a structure or alike. */
|
||||
void *arg1, *arg2, *arg3;
|
||||
/* Job specific arguments.*/
|
||||
int fd; /* Fd for file based background jobs */
|
||||
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 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
|
||||
* main thread. */
|
||||
@ -128,13 +126,8 @@ void bioInit(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
|
||||
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job), MALLOC_LOCAL);
|
||||
|
||||
void bioSubmitJob(int type, struct bio_job *job) {
|
||||
job->time = time(NULL);
|
||||
job->arg1 = arg1;
|
||||
job->arg2 = arg2;
|
||||
job->arg3 = arg3;
|
||||
pthread_mutex_lock(&bio_mutex[type]);
|
||||
listAddNodeTail(bio_jobs[type],job);
|
||||
bio_pending[type]++;
|
||||
@ -142,6 +135,35 @@ void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
|
||||
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) {
|
||||
struct bio_job *job;
|
||||
unsigned long type = (unsigned long) arg;
|
||||
@ -196,20 +218,11 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
|
||||
/* Process the job accordingly to its type. */
|
||||
if (type == BIO_CLOSE_FILE) {
|
||||
close((long)job->arg1);
|
||||
close(job->fd);
|
||||
} else if (type == BIO_AOF_FSYNC) {
|
||||
redis_fsync((long)job->arg1);
|
||||
redis_fsync(job->fd);
|
||||
} else if (type == BIO_LAZY_FREE) {
|
||||
/* What we free changes depending on what arguments are set:
|
||||
* 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);
|
||||
job->free_fn(job->free_args);
|
||||
} else {
|
||||
serverPanic("Wrong job type in bioProcessBackgroundJobs().");
|
||||
}
|
||||
|
@ -32,13 +32,17 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void lazy_free_fn(void *args[]);
|
||||
|
||||
/* Exported API */
|
||||
void bioInit(void);
|
||||
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
|
||||
unsigned long long bioPendingJobsOfType(int type);
|
||||
unsigned long long bioWaitStepOfType(int type);
|
||||
time_t bioOlderJobOfType(int type);
|
||||
void bioKillThreads(void);
|
||||
void bioCreateCloseJob(int fd);
|
||||
void bioCreateFsyncJob(int fd);
|
||||
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...);
|
||||
|
||||
/* Background job opcodes */
|
||||
#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' 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 bits = 0;
|
||||
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
|
||||
* 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
|
||||
* 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. */
|
||||
if (usehash) loffset *= bits;
|
||||
|
||||
/* Limit offset to 512MB in bytes */
|
||||
if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
|
||||
/* Limit offset to server.proto_max_bulk_len (512MB in bytes by default) */
|
||||
if ((loffset < 0) || (loffset >> 3) >= g_pserver->proto_max_bulk_len)
|
||||
{
|
||||
addReplyError(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) {
|
||||
size_t byte = maxbit >> 3;
|
||||
robj *o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (checkType(c,o,OBJ_STRING)) return NULL;
|
||||
|
||||
if (o == NULL) {
|
||||
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
} else {
|
||||
if (checkType(c,o,OBJ_STRING)) return NULL;
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
o->m_ptr = sdsgrowzero(szFromObj(o),byte+1);
|
||||
}
|
||||
@ -619,7 +619,7 @@ void bitopCommand(client *c) {
|
||||
else if (!strcasecmp(opname, "rshift"))
|
||||
op = BITOP_RSHIFT;
|
||||
else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -731,7 +731,6 @@ void bitopCommand(client *c) {
|
||||
/* Compute the bit operation, if at least one string is not empty. */
|
||||
if (maxlen) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte;
|
||||
unsigned long i;
|
||||
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
@ -801,22 +800,33 @@ void bitopCommand(client *c) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/* j is set to the next byte to process by the previous loop. */
|
||||
for (; j < maxlen; j++) {
|
||||
output = (len[0] <= j) ? 0 : src[0][j];
|
||||
if (op == BITOP_NOT) output = ~output;
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
byte = (len[i] <= j) ? 0 : src[i][j];
|
||||
switch(op) {
|
||||
case BITOP_AND: output &= byte; break;
|
||||
case BITOP_OR: output |= byte; break;
|
||||
case BITOP_XOR: output ^= byte; break;
|
||||
}
|
||||
/* j is set to the next byte to process by the previous loop. */
|
||||
for (; j < maxlen; j++) {
|
||||
auto output = (len[0] <= j) ? 0 : src[0][j];
|
||||
if (op == BITOP_NOT) output = ~output;
|
||||
for (unsigned long i = 1; i < numkeys; i++) {
|
||||
int skip = 0;
|
||||
auto byte = (len[i] <= j) ? 0 : src[i][j];
|
||||
switch(op) {
|
||||
case BITOP_AND:
|
||||
output &= byte;
|
||||
skip = (output == 0);
|
||||
break;
|
||||
case BITOP_OR:
|
||||
output |= byte;
|
||||
skip = (output == 0xff);
|
||||
break;
|
||||
case BITOP_XOR: output ^= byte; break;
|
||||
}
|
||||
|
||||
if (skip) {
|
||||
break;
|
||||
}
|
||||
res[j] = output;
|
||||
}
|
||||
res[j] = output;
|
||||
}
|
||||
}
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
@ -876,7 +886,7 @@ void bitcountCommand(client *c) {
|
||||
end = strlen-1;
|
||||
} else {
|
||||
/* Syntax error. */
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -941,7 +951,7 @@ void bitposCommand(client *c) {
|
||||
end = strlen-1;
|
||||
} else {
|
||||
/* Syntax error. */
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1033,7 +1043,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
|
120
src/blocked.cpp
120
src/blocked.cpp
@ -61,9 +61,13 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "slowlog.h"
|
||||
#include "latency.h"
|
||||
#include "monotonic.h"
|
||||
#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
|
||||
* 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_by_type[btype]++;
|
||||
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
|
||||
@ -118,6 +142,11 @@ void processUnblockedClients(int iel) {
|
||||
* client is not blocked before to proceed, but things may change and
|
||||
* the code is conceptually more correct this way. */
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
/* If we have a queued command, execute it now. */
|
||||
if (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) {
|
||||
processInputBuffer(c, CMD_CALL_FULL);
|
||||
}
|
||||
@ -168,6 +197,9 @@ void unblockClient(client *c) {
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
|
||||
unblockClientFromModule(c);
|
||||
} else if (c->btype == BLOCKED_PAUSE) {
|
||||
listDelNode(g_pserver->paused_clients,c->paused_list_node);
|
||||
c->paused_list_node = NULL;
|
||||
} else {
|
||||
serverPanic("Unknown btype in unblockClient().");
|
||||
}
|
||||
@ -216,9 +248,16 @@ void disconnectAllBlockedClients(void) {
|
||||
|
||||
fastlock_lock(&c->lock);
|
||||
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, "
|
||||
"instance state changed (master -> replica?)\r\n"));
|
||||
"instance state changed (master -> replica?)");
|
||||
unblockClient(c);
|
||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||
}
|
||||
@ -250,10 +289,9 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
}
|
||||
|
||||
robj *dstkey = receiver->bpop.target;
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == blpopCommand) ?
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
robj *value = listTypePop(o,where);
|
||||
int wherefrom = receiver->bpop.listpos.wherefrom;
|
||||
int whereto = receiver->bpop.listpos.whereto;
|
||||
robj *value = listTypePop(o, wherefrom);
|
||||
|
||||
if (value) {
|
||||
/* Protect receiver->bpop.target, that will be
|
||||
@ -262,14 +300,17 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
unblockClient(receiver);
|
||||
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
rl->key,dstkey,rl->db,value,
|
||||
where) == C_ERR)
|
||||
wherefrom, whereto) == C_ERR)
|
||||
{
|
||||
/* If we failed serving the client we need
|
||||
* to also undo the POP operation. */
|
||||
listTypePush(o,value,where);
|
||||
listTypePush(o,value,wherefrom);
|
||||
}
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
decrRefCount(value);
|
||||
@ -315,7 +356,10 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
||||
receiver->lastcmd->proc == bzpopminCommand)
|
||||
? ZSET_MIN : ZSET_MAX;
|
||||
unblockClient(receiver);
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
zcard--;
|
||||
|
||||
/* Replicate the command. */
|
||||
@ -392,13 +436,22 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
int created = 0;
|
||||
consumer =
|
||||
streamLookupConsumer(group,
|
||||
szFromObj(receiver->bpop.xread_consumer),
|
||||
SLC_NONE);
|
||||
SLC_NONE,
|
||||
&created);
|
||||
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
|
||||
* the name of the stream and the data we
|
||||
* extracted from it. Wrapped in a single-item
|
||||
@ -418,6 +471,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, noack, &pi);
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* 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
|
||||
* is ready or not. This means we can't exit the loop but need
|
||||
* to continue after the first failure. */
|
||||
monotime replyTimer;
|
||||
elapsedStart(&replyTimer);
|
||||
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
|
||||
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
|
||||
|
||||
moduleUnblockClient(receiver);
|
||||
}
|
||||
@ -480,8 +537,8 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
* one new element via some write operation are accumulated into
|
||||
* 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
|
||||
* again as a result of serving BRPOPLPUSH we can have new blocking clients
|
||||
* to serve because of the PUSH side of BRPOPLPUSH.
|
||||
* again as a result of serving BLMOVE we can have new blocking clients
|
||||
* to serve because of the PUSH side of BLMOVE.
|
||||
*
|
||||
* This function is normally "fair", that is, it will server clients
|
||||
* 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
|
||||
* in order to make sure that keys are expired against a fixed
|
||||
* 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
|
||||
* lookup, invalidating the first one.
|
||||
* 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
|
||||
* be unblocked only when items with an ID greater or equal to the specified
|
||||
* 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;
|
||||
list *l;
|
||||
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.target = target;
|
||||
|
||||
if (listpos != NULL) c->bpop.listpos = *listpos;
|
||||
|
||||
if (target != NULL) incrRefCount(target);
|
||||
|
||||
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
|
||||
* 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
|
||||
@ -663,9 +732,24 @@ void unblockClientWaitingData(client *c) {
|
||||
* made by a script or in the context of MULTI/EXEC.
|
||||
*
|
||||
* The list will be finally processed by handleClientsBlockedOnKeys() */
|
||||
void signalKeyAsReady(redisDb *db, robj *key) {
|
||||
void signalKeyAsReady(redisDb *db, robj *key, int type) {
|
||||
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. */
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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 <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
|
||||
* RDB / AOF saving process from the child to the parent (for instance
|
||||
* the amount of copy on write memory used) */
|
||||
void openChildInfoPipe(void) {
|
||||
if (pipe(g_pserver->child_info_pipe) == -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();
|
||||
} else if (anetNonBlock(NULL,g_pserver->child_info_pipe[0]) != ANET_OK) {
|
||||
closeChildInfoPipe();
|
||||
} 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]);
|
||||
g_pserver->child_info_pipe[0] = -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
|
||||
* the corresponding fields it want to sent (according to the process type). */
|
||||
void sendChildInfo(int ptype) {
|
||||
/* Send save data to parent. */
|
||||
void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress, const char *pname) {
|
||||
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;
|
||||
ssize_t wlen = sizeof(g_pserver->child_info_data);
|
||||
if (write(g_pserver->child_info_pipe[1],&g_pserver->child_info_data,wlen) != wlen) {
|
||||
|
||||
child_info_data data = {0}; /* zero everything, including padding to sattisfy valgrind */
|
||||
data.information_type = info_type;
|
||||
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. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Receive COW data from parent. */
|
||||
void receiveChildInfo(void) {
|
||||
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 &&
|
||||
g_pserver->child_info_data.magic == CHILD_INFO_MAGIC)
|
||||
{
|
||||
if (g_pserver->child_info_data.process_type == CHILD_TYPE_RDB) {
|
||||
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) {
|
||||
g_pserver->stat_aof_cow_bytes = g_pserver->child_info_data.cow_size;
|
||||
} else if (g_pserver->child_info_data.process_type == CHILD_TYPE_MODULE) {
|
||||
g_pserver->stat_module_cow_bytes = g_pserver->child_info_data.cow_size;
|
||||
}
|
||||
/* 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) {
|
||||
if (g_pserver->child_info_pipe[0] == -1) return;
|
||||
|
||||
size_t cow;
|
||||
size_t keys;
|
||||
double progress;
|
||||
childInfoType information_type;
|
||||
|
||||
/* Drain the pipe and update child info so that we get the final message. */
|
||||
while (readChildInfo(&information_type, &cow, &keys, &progress)) {
|
||||
updateChildInfo(information_type, cow, keys, progress);
|
||||
}
|
||||
}
|
||||
|
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 */
|
308
src/cluster.cpp
308
src/cluster.cpp
@ -47,7 +47,7 @@
|
||||
clusterNode *myself = NULL;
|
||||
|
||||
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 clusterReadHandler(connection *conn);
|
||||
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
|
||||
* it does not exist, otherwise there is a race condition with other
|
||||
* processes. */
|
||||
int fd = open(filename,O_WRONLY|O_CREAT,0644);
|
||||
int fd = open(filename,O_WRONLY|O_CREAT|O_CLOEXEC,0644);
|
||||
if (fd == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Can't open %s in order to acquire a lock: %s",
|
||||
@ -519,7 +519,7 @@ void clusterInit(void) {
|
||||
if (saveconf) clusterSaveConfigOrDie(1);
|
||||
|
||||
/* 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
|
||||
* 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. "
|
||||
"Cluster communication port is 10,000 port "
|
||||
"numbers higher than your KeyDB port. "
|
||||
"Your KeyDB port number must be "
|
||||
"lower than 55535.");
|
||||
"Your KeyDB port number must be 55535 or less.");
|
||||
exit(1);
|
||||
}
|
||||
if (listenToPort(port+CLUSTER_PORT_INCR,
|
||||
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR)
|
||||
{
|
||||
if (listenToPort(port+CLUSTER_PORT_INCR, &g_pserver->cfd, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR) {
|
||||
exit(1);
|
||||
} else {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < g_pserver->cfd_count; j++) {
|
||||
if (aeCreateFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->cfd[j], AE_READABLE,
|
||||
clusterAcceptHandler, NULL) == AE_ERR)
|
||||
serverPanic("Unrecoverable error creating Redis Cluster "
|
||||
"file event.");
|
||||
}
|
||||
}
|
||||
|
||||
serverAssert(serverTL == &g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN]);
|
||||
if (createSocketAcceptHandler(&g_pserver->cfd, clusterAcceptHandler) != C_OK) {
|
||||
serverPanic("Unrecoverable error creating Redis Cluster socket accept handler.");
|
||||
}
|
||||
|
||||
/* 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->flags = flags;
|
||||
memset(node->slots,0,sizeof(node->slots));
|
||||
node->slots_info = NULL;
|
||||
node->numslots = 0;
|
||||
node->numslaves = 0;
|
||||
node->slaves = NULL;
|
||||
@ -997,12 +991,12 @@ void freeClusterNode(clusterNode *n) {
|
||||
}
|
||||
|
||||
/* Add a node to the nodes hash table */
|
||||
int clusterAddNode(clusterNode *node) {
|
||||
void clusterAddNode(clusterNode *node) {
|
||||
int retval;
|
||||
|
||||
retval = dictAdd(g_pserver->cluster->nodes,
|
||||
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
|
||||
@ -1862,6 +1856,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
g_pserver->cluster->mf_master_offset == 0)
|
||||
{
|
||||
g_pserver->cluster->mf_master_offset = sender->repl_offset;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER);
|
||||
serverLog(LL_WARNING,
|
||||
"Received replication offset for paused "
|
||||
"master manual failover: %lld",
|
||||
@ -2165,7 +2160,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* Don't bother creating useless objects if there are no
|
||||
* Pub/Sub subscribers. */
|
||||
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);
|
||||
message_len = ntohl(hdr->data.publish.msg.message_len);
|
||||
@ -2203,9 +2198,15 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
resetManualFailover();
|
||||
g_pserver->cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
||||
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.",
|
||||
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) {
|
||||
clusterNode *n; /* The node the update is about. */
|
||||
uint64_t reportedConfigEpoch =
|
||||
@ -2856,7 +2857,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
|
||||
* 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
|
||||
* master.
|
||||
*
|
||||
@ -3462,9 +3463,8 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
* The function can be used both to initialize the manual failover state at
|
||||
* startup or to abort a manual failover in progress. */
|
||||
void resetManualFailover(void) {
|
||||
if (g_pserver->cluster->mf_end && clientsArePaused()) {
|
||||
g_pserver->clients_pause_end_time = 0;
|
||||
unpauseClientsIfNecessary();
|
||||
if (g_pserver->cluster->mf_end) {
|
||||
checkClientPauseTimeoutAndReturnIfPaused();
|
||||
}
|
||||
g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */
|
||||
g_pserver->cluster->mf_can_start = 0;
|
||||
@ -3499,7 +3499,10 @@ void clusterHandleManualFailover(void) {
|
||||
serverLog(LL_WARNING,
|
||||
"All master replication stream processed, "
|
||||
"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
|
||||
* a single time before replying to clients. */
|
||||
void clusterBeforeSleep(void) {
|
||||
/* Handle failover, this is needed when it is likely that there is already
|
||||
* 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);
|
||||
}
|
||||
int flags = g_pserver->cluster->todo_before_sleep;
|
||||
|
||||
/* Reset our flags (not strictly needed since every single function
|
||||
* called for flags set should be able to clear its flag). */
|
||||
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) {
|
||||
@ -4173,8 +4186,8 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
||||
sds ci;
|
||||
|
||||
/* Node coordinates */
|
||||
ci = sdscatprintf(sdsempty(),"%.40s %s:%d@%d ",
|
||||
node->name,
|
||||
ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
|
||||
ci = sdscatfmt(ci," %s:%i@%i ",
|
||||
node->ip,
|
||||
node->port,
|
||||
node->cport);
|
||||
@ -4183,40 +4196,46 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
||||
ci = representClusterNodeFlags(ci, node->flags);
|
||||
|
||||
/* Slave of... or just "-" */
|
||||
ci = sdscatlen(ci," ",1);
|
||||
if (node->slaveof)
|
||||
ci = sdscatprintf(ci," %.40s ",node->slaveof->name);
|
||||
ci = sdscatlen(ci,node->slaveof->name,CLUSTER_NAMELEN);
|
||||
else
|
||||
ci = sdscatlen(ci," - ",3);
|
||||
ci = sdscatlen(ci,"-",1);
|
||||
|
||||
unsigned long long nodeEpoch = node->configEpoch;
|
||||
if (nodeIsSlave(node) && node->slaveof) {
|
||||
nodeEpoch = node->slaveof->configEpoch;
|
||||
}
|
||||
/* Latency from the POV of this node, config epoch, link status */
|
||||
ci = sdscatprintf(ci,"%lld %lld %llu %s",
|
||||
ci = sdscatfmt(ci," %I %I %U %s",
|
||||
(long long) node->ping_sent,
|
||||
(long long) node->pong_received,
|
||||
nodeEpoch,
|
||||
(node->link || node->flags & CLUSTER_NODE_MYSELF) ?
|
||||
"connected" : "disconnected");
|
||||
|
||||
/* Slots served by this instance */
|
||||
start = -1;
|
||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||
int bit;
|
||||
/* 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;
|
||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||
int bit;
|
||||
|
||||
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {
|
||||
if (start == -1) start = j;
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
if (start == j-1) {
|
||||
ci = sdscatprintf(ci," %d",start);
|
||||
} else {
|
||||
ci = sdscatprintf(ci," %d-%d",start,j-1);
|
||||
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {
|
||||
if (start == -1) start = j;
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
if (start == j-1) {
|
||||
ci = sdscatfmt(ci," %i",start);
|
||||
} else {
|
||||
ci = sdscatfmt(ci," %i-%i",start,j-1);
|
||||
}
|
||||
start = -1;
|
||||
}
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4237,6 +4256,41 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
||||
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,
|
||||
* including the "myself" node, and return an SDS string containing the
|
||||
* representation (it is up to the caller to free it).
|
||||
@ -4254,6 +4308,9 @@ sds clusterGenNodesDescription(int filter) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
/* Generate all nodes slots info firstly. */
|
||||
clusterGenNodesSlotsInfo(filter);
|
||||
|
||||
di = dictGetSafeIterator(g_pserver->cluster->nodes);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = (clusterNode*)dictGetVal(de);
|
||||
@ -4263,6 +4320,12 @@ sds clusterGenNodesDescription(int filter) {
|
||||
ci = sdscatsds(ci,ni);
|
||||
sdsfree(ni);
|
||||
ci = sdscatlen(ci,"\n",1);
|
||||
|
||||
/* Release slots info. */
|
||||
if (node->slots_info) {
|
||||
sdsfree(node->slots_info);
|
||||
node->slots_info = NULL;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return ci;
|
||||
@ -4385,28 +4448,49 @@ void clusterCommand(client *c) {
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
|
||||
const char *help[] = {
|
||||
"ADDSLOTS <slot> [slot ...] -- Assign slots to current node.",
|
||||
"BUMPEPOCH -- Advance the cluster config epoch.",
|
||||
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
|
||||
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.",
|
||||
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.",
|
||||
"FAILOVER [force|takeover] -- Promote current replica node to being a master.",
|
||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
||||
"INFO - Return information about the cluster.",
|
||||
"KEYSLOT <key> -- 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> ... <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:",
|
||||
"ADDSLOTS <slot> [<slot> ...]",
|
||||
" Assign slots to current node.",
|
||||
"BUMPEPOCH",
|
||||
" Advance the cluster config epoch.",
|
||||
"COUNT-FAILURE-REPORTS <node-id>",
|
||||
" Return number of failure reports for <node-id>.",
|
||||
"COUNTKEYSINSLOT <slot>",
|
||||
" Return the number of keys in <slot>.",
|
||||
"DELSLOTS <slot> [<slot> ...]",
|
||||
" Delete slots information from current node.",
|
||||
"FAILOVER [FORCE|TAKEOVER]",
|
||||
" Promote current replica node to being a master.",
|
||||
"FORGET <node-id>",
|
||||
" Remove a node from the cluster.",
|
||||
"GETKEYSINSLOT <slot> <count>",
|
||||
" Return key names stored by current node in a slot.",
|
||||
"FLUSHSLOTS",
|
||||
" Delete current node own slots information.",
|
||||
"INFO",
|
||||
" Return information about the cluster.",
|
||||
"KEYSLOT <key>",
|
||||
" 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",
|
||||
NULL
|
||||
};
|
||||
@ -4577,6 +4661,9 @@ NULL
|
||||
g_pserver->cluster->migrating_slots_to[slot])
|
||||
g_pserver->cluster->migrating_slots_to[slot] = NULL;
|
||||
|
||||
clusterDelSlot(slot);
|
||||
clusterAddSlot(n,slot);
|
||||
|
||||
/* If this node was importing this slot, assigning the slot to
|
||||
* itself also clears the importing status. */
|
||||
if (n == myself &&
|
||||
@ -4596,9 +4683,10 @@ NULL
|
||||
"configEpoch updated after importing slot %d", slot);
|
||||
}
|
||||
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 {
|
||||
addReplyError(c,
|
||||
"Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP");
|
||||
@ -4844,7 +4932,7 @@ NULL
|
||||
takeover = 1;
|
||||
force = 1; /* Takeover also implies force. */
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -4935,7 +5023,7 @@ NULL
|
||||
} else if (!strcasecmp(szFromObj(c->argv[2]),"soft")) {
|
||||
hard = 0;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -5007,6 +5095,9 @@ int verifyDumpPayload(unsigned char *p, size_t len) {
|
||||
rdbver = (footer[1] << 8) | footer[0];
|
||||
if (rdbver > RDB_VERSION) return C_ERR;
|
||||
|
||||
if (cserver.skip_checksum_validation)
|
||||
return C_OK;
|
||||
|
||||
/* Verify CRC64 */
|
||||
crc = crc64(0,p,len-8);
|
||||
memrev64ifbe(&crc);
|
||||
@ -5066,7 +5157,7 @@ void mvccrestoreCommand(client *c) {
|
||||
setMvccTstamp(obj, mvcc);
|
||||
|
||||
/* 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) {
|
||||
setExpire(c,c->db,key,nullptr,expire);
|
||||
}
|
||||
@ -5115,7 +5206,7 @@ void restoreCommand(client *c) {
|
||||
}
|
||||
j++; /* Consume additional arg. */
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -5123,7 +5214,7 @@ void restoreCommand(client *c) {
|
||||
/* Make sure this key does not already exist here... */
|
||||
robj *key = c->argv[1];
|
||||
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
|
||||
addReply(c,shared.busykeyerr);
|
||||
addReplyErrorObject(c,shared.busykeyerr);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -5236,8 +5327,7 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
|
||||
conn = g_pserver->tls_cluster ? connCreateTLS() : connCreateSocket();
|
||||
if (connBlockingConnect(conn, szFromObj(c->argv[1]), atoi(szFromObj(c->argv[2])), timeout)
|
||||
!= C_OK) {
|
||||
addReplySds(c,
|
||||
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
||||
addReplyError(c,"-IOERR error or timeout connecting to the client");
|
||||
connClose(conn);
|
||||
sdsfree(name);
|
||||
return NULL;
|
||||
@ -5324,7 +5414,7 @@ void migrateCommand(client *c) {
|
||||
replace = 1;
|
||||
} else if (!strcasecmp(szFromObj(c->argv[j]),"auth")) {
|
||||
if (!moreargs) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
j++;
|
||||
@ -5347,7 +5437,7 @@ void migrateCommand(client *c) {
|
||||
num_keys = c->argc - j - 1;
|
||||
break; /* All the remaining args are keys. */
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -5840,7 +5930,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
* cluster is down. */
|
||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
||||
return NULL;
|
||||
} else if (!(cmd->flags & CMD_READONLY) && !(cmd->proc == evalCommand)
|
||||
} else if ((cmd->flags & CMD_WRITE) && !(cmd->proc == evalCommand)
|
||||
&& !(cmd->proc == evalShaCommand))
|
||||
{
|
||||
/* 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
|
||||
* node is a slave and the request is about a hash slot our master
|
||||
* is serving, we can reply without redirection. */
|
||||
int is_readonly_command = (c->cmd->flags & CMD_READONLY) ||
|
||||
(c->cmd->proc == execCommand && !(c->mstate.cmd_inv_flags & CMD_READONLY));
|
||||
int is_write_command = (c->cmd->flags & CMD_WRITE) ||
|
||||
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
|
||||
if (c->flags & CLIENT_READONLY &&
|
||||
(is_readonly_command || cmd->proc == evalCommand ||
|
||||
cmd->proc == evalShaCommand) &&
|
||||
(!is_write_command || cmd->proc == evalCommand || cmd->proc == evalShaCommand) &&
|
||||
nodeIsSlave(myself) &&
|
||||
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. */
|
||||
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) {
|
||||
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) {
|
||||
/* The request spawns multiple keys in the same slot,
|
||||
* but the slot is not "stable" currently as there is
|
||||
* a migration or import in progress. */
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
addReplySds(c,sdsnew("-CLUSTERDOWN Hash slot not served\r\n"));
|
||||
addReplyError(c,"-CLUSTERDOWN Hash slot not served");
|
||||
} else if (error_code == CLUSTER_REDIR_MOVED ||
|
||||
error_code == CLUSTER_REDIR_ASK)
|
||||
{
|
||||
addReplySds(c,sdscatprintf(sdsempty(),
|
||||
"-%s %d %s:%d\r\n",
|
||||
addReplyErrorSds(c,sdscatprintf(sdsempty(),
|
||||
"-%s %d %s:%d",
|
||||
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
||||
hashslot,n->ip,n->port));
|
||||
} else {
|
||||
@ -5985,6 +6074,15 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
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:
|
||||
* 1) The slot is unassigned, emitting a cluster down error.
|
||||
* 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_SAVE_CONFIG (1<<2)
|
||||
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
|
||||
#define CLUSTER_TODO_HANDLE_MANUALFAILOVER (1<<4)
|
||||
|
||||
/* Message types.
|
||||
*
|
||||
@ -121,6 +122,7 @@ typedef struct clusterNode {
|
||||
int flags; /* CLUSTER_NODE_... */
|
||||
uint64_t configEpoch; /* Last configEpoch observed for 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 numslaves; /* Number of slave nodes, if this is a master */
|
||||
struct clusterNode **slaves; /* pointers to slave nodes */
|
||||
|
@ -73,7 +73,7 @@ public:
|
||||
m_max = m_max + 4;
|
||||
|
||||
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);
|
||||
where = m_data + idx;
|
||||
|
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