Compare commits
130
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c50a3d0bbd | ||
|
|
5eb84db03a | ||
|
|
bd9d8e05b0 | ||
|
|
2ab4c6b99e | ||
|
|
c12be59475 | ||
|
|
fcdda67d87 | ||
|
|
dfdd41f15c | ||
|
|
e8debc83e8 | ||
|
|
88152a8f4f | ||
|
|
f5210a8fb0 | ||
|
|
8f1295c00f | ||
|
|
18a3768e07 | ||
|
|
c4f086ae93 | ||
|
|
3f0c1f94eb | ||
|
|
f6855bc34d | ||
|
|
95e3dbc104 | ||
|
|
40355eac3e | ||
|
|
898efbfc0c | ||
|
|
8d63f5b560 | ||
|
|
024365521c | ||
|
|
3a3b59a054 | ||
|
|
12e17cfd6e | ||
|
|
203e341bf4 | ||
|
|
6e6cd84ad7 | ||
|
|
252725d50f | ||
|
|
c15633ea9e | ||
|
|
27c9ae667e | ||
|
|
16f96efbe7 | ||
|
|
5787e7adc7 | ||
|
|
4631119179 | ||
|
|
cedea7e400 | ||
|
|
64aa4b80e6 | ||
|
|
551a3cb1aa | ||
|
|
0942188b0a | ||
|
|
070c481871 | ||
|
|
6df4d29422 | ||
|
|
6e1e5bd08b | ||
|
|
ba365298ed | ||
|
|
e1c9620951 | ||
|
|
e776474f68 | ||
|
|
cbd4b4df19 | ||
|
|
08995b065c | ||
|
|
065846a9c3 | ||
|
|
55324aa56a | ||
|
|
18d42a5c35 | ||
|
|
ac595ebe37 | ||
|
|
46a78c69e7 | ||
|
|
33e0b3e1c0 | ||
|
|
4e9bfcf84b | ||
|
|
75c6b4c64a | ||
|
|
8536854266 | ||
|
|
6016dd0f8b | ||
|
|
47ef9e5cbd | ||
|
|
4858fd893c | ||
|
|
cbe5c04a0f | ||
|
|
08b6a04055 | ||
|
|
afe66288fe | ||
|
|
2794cfced4 | ||
|
|
d6b45a56e7 | ||
|
|
a55d980436 | ||
|
|
6037d1f326 | ||
|
|
0b43b51a2e | ||
|
|
184abac694 | ||
|
|
0ba5b2c3d6 | ||
|
|
0b3d74ea67 | ||
|
|
ea3bd614b8 | ||
|
|
d45edc493d | ||
|
|
271df3dad4 | ||
|
|
5a193872f8 | ||
|
|
b5512d77a1 | ||
|
|
42bda8eaba | ||
|
|
7e5d3f4f16 | ||
|
|
5f3920e491 | ||
|
|
3880d2616c | ||
|
|
156e596f9c | ||
|
|
fc58516cca | ||
|
|
a4efcd35af | ||
|
|
27fe1658a2 | ||
|
|
47feb2719c | ||
|
|
c21af6b351 | ||
|
|
821c12c482 | ||
|
|
4602f8c391 | ||
|
|
27a927fe00 | ||
|
|
3cdc6e8d84 | ||
|
|
89b52da387 | ||
|
|
50ad880ad5 | ||
|
|
15bed4bbcc | ||
|
|
10986ae3a8 | ||
|
|
2baa76b2ee | ||
|
|
ee460301fd | ||
|
|
8ebe2fce3d | ||
|
|
8198a697fd | ||
|
|
35acae360a | ||
|
|
8aaa8b0b11 | ||
|
|
722446510f | ||
|
|
6eb52e200c | ||
|
|
4c4b7023dd | ||
|
|
2d07883cab | ||
|
|
e8ae07aabf | ||
|
|
677e95e2f5 | ||
|
|
a714004738 | ||
|
|
6b29f2d83d | ||
|
|
f099def733 | ||
|
|
3e43aed1ac | ||
|
|
46edb55de9 | ||
|
|
506764b3f8 | ||
|
|
db16a861a1 | ||
|
|
c29f3bcf2e | ||
|
|
45d64f229e | ||
|
|
a28d7918d7 | ||
|
|
dd51fc5a4a | ||
|
|
38cd5fd9f6 | ||
|
|
f54d95e803 | ||
|
|
e2adea2188 | ||
|
|
0cabe0cfa7 | ||
|
|
dd5f437871 | ||
|
|
de035c9481 | ||
|
|
d3ed53f11b | ||
|
|
bf963253ec | ||
|
|
fd0ee469ab | ||
|
|
7fababd44e | ||
|
|
fd9407dfc8 | ||
|
|
074d24df1e | ||
|
|
caf74e507e | ||
|
|
1a1ba48390 | ||
|
|
9724ca4e22 | ||
|
|
4f4676a142 | ||
|
|
11f2c6b115 | ||
|
|
c184b32d4c | ||
|
|
26d9d4ec2c |
No files matched your search
Vendored
+3
-1
@@ -51,6 +51,8 @@
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"utility": "cpp"
|
||||
"utility": "cpp",
|
||||
"set": "cpp",
|
||||
"algorithm": "cpp"
|
||||
}
|
||||
}
|
||||
+15
-5
@@ -14,9 +14,7 @@ each source file that you contribute.
|
||||
|
||||
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
|
||||
bugs in the Github issues system. We'll be very happy to help you and provide
|
||||
all the support at the Reddit sub:
|
||||
|
||||
http://reddit.com/r/redis
|
||||
all the support in the mailing list.
|
||||
|
||||
There is also an active community of Redis users at Stack Overflow:
|
||||
|
||||
@@ -24,7 +22,12 @@ each source file that you contribute.
|
||||
|
||||
# How to provide a patch for a new feature
|
||||
|
||||
1. If it is a major feature or a semantical change, please post it as a new submission in r/redis on Reddit at http://reddit.com/r/redis. Try to be passionate about why the feature is needed, make users upvote your proposal to gain traction and so forth. Read feedbacks about the community. But in this first step **please don't write code yet**.
|
||||
1. If it is a major feature or a semantical change, please don't start coding
|
||||
straight away: if your feature is not a conceptual fit you'll lose a lot of
|
||||
time writing the code without any reason. Start by posting in the mailing list
|
||||
and creating an issue at Github with the description of, exactly, what you want
|
||||
to accomplish and why. Use cases are important for features to be accepted.
|
||||
Here you'll see if there is consensus about your idea.
|
||||
|
||||
2. If in step 1 you get an acknowledgment from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
@@ -35,6 +38,13 @@ each source file that you contribute.
|
||||
d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
|
||||
e. Done :)
|
||||
|
||||
For minor fixes just open a pull request on Github.
|
||||
3. Keep in mind that we are very overloaded, so issues and PRs sometimes wait
|
||||
for a *very* long time. However this is not lack of interest, as the project
|
||||
gets more and more users, we find ourselves in a constant need to prioritize
|
||||
certain issues/PRs over others. If you think your issue/PR is very important
|
||||
try to popularize it, have other users commenting and sharing their point of
|
||||
view and so forth. This helps.
|
||||
|
||||
4. For minor fixes just open a pull request on Github.
|
||||
|
||||
Thanks!
|
||||
+1
-1
@@ -4,7 +4,7 @@ FROM ubuntu:18.04
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -qqy \
|
||||
build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev \
|
||||
build-essential nasm autotools-dev autoconf libcurl4-openssl-dev libjemalloc-dev tcl tcl-dev uuid-dev \
|
||||
&& apt-get clean
|
||||
|
||||
CMD make
|
||||
@@ -2,40 +2,47 @@
|
||||
[](https://travis-ci.org/JohnSully/KeyDB) [](https://gitter.im/KeyDB/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://stackshare.io/eq-alpha-technology-inc/eq-alpha-technology-inc)
|
||||
|
||||
##### Need Help? Check out our extensive [documentation](https://docs.keydb.dev).
|
||||
|
||||
What is KeyDB?
|
||||
--------------
|
||||
|
||||
KeyDB is a high performance fork of Redis focusing on multithreading, memory efficiency, and high throughput. In addition to multithreading KeyDB also has features only available in Redis Enterprise such as [Active Replication](https://github.com/JohnSully/KeyDB/wiki/Active-Replication), [FLASH storage](https://github.com/JohnSully/KeyDB/wiki/FLASH-Storage) support, and some not available at all such as direct backup to AWS S3.
|
||||
KeyDB is a high performance fork of Redis with a focus on multithreading, memory efficiency, and high throughput. In addition to multithreading, KeyDB also has features only available in Redis Enterprise such as [Active Replication](https://github.com/JohnSully/KeyDB/wiki/Active-Replication), [FLASH storage](https://github.com/JohnSully/KeyDB/wiki/FLASH-Storage) support, and some not available at all such as direct backup to AWS S3.
|
||||
|
||||
On the same hardware KeyDB can perform twice as many queries per second as Redis, with 60% lower latency.
|
||||
KeyDB maintains full compatibility with the Redis protocol, modules, and scripts. This includes the atomicity gurantees for scripts and transactions. Because KeyDB keeps in sync with Redis development KeyDB is a superset of Redis functionality, making KeyDB a drop in replacement for existing Redis deployments.
|
||||
|
||||
KeyDB has full compatibility with the Redis protocol, modules, and scripts. This includes full support for transactions, and atomic execution of scripts. For more information see our architecture section below.
|
||||
On the same hardware KeyDB can perform twice as many queries per second as Redis, with 60% lower latency. Active-Replication simplifies hot-spare failover allowing you to easily distribute writes over replicas and use simple TCP based load balancing/failover. KeyDB's higher performance allows you to do more on less hardware which reduces operation costs and complexity.
|
||||
|
||||
<img src=https://cdn-images-1.medium.com/max/1400/1*s7mTb7Qb0kxc951mz8bdgA.png width=420 height=300/><img src=https://cdn-images-1.medium.com/max/1400/1*R00A5U4AFGohGOYHMfT6fA.png height=300/>
|
||||
|
||||
Why fork Redis?
|
||||
---------------
|
||||
|
||||
KeyDB has a different philosophy on how the codebase should evolve. We feel that ease of use, high performance, and a "batteries included" approach is the best way to create a good user experience. While we have great respect for the Redis maintainers it is our opinion that the Redis approach focuses too much on simplicity of the code base at the expense of complexity for the user. This results in the need for external components and workarounds to solve common problems - resulting in more complexity overall.
|
||||
|
||||
Because of this difference of opinion features which are right for KeyDB may not be appropriate for Redis. A fork allows us to explore this new development path and implement features which may never be a part of Redis. KeyDB keeps in sync with upstream Redis changes, and where applicable we upstream bug fixes and changes. It is our hope that the two projects can continue to grow and learn from each other.
|
||||
|
||||
Additional Resources
|
||||
--------------------
|
||||
|
||||
Try our docker container: https://hub.docker.com/r/eqalpha/keydb
|
||||
|
||||
Talk on Gitter: https://gitter.im/KeyDB
|
||||
|
||||
Visit our Website: https://keydb.dev
|
||||
|
||||
See options for channel partners and support contracts: https://keydb.dev/support.html
|
||||
|
||||
Learn with KeyDB’s official documentation site: https://docs.keydb.dev
|
||||
|
||||
[Subscribe to the KeyDB mailing list](https://eqalpha.us20.list-manage.com/subscribe/post?u=978f486c2f95589b24591a9cc&id=4ab9220500)
|
||||
|
||||
Management GUI: We recommend [FastoNoSQL](https://fastonosql.com/) which has official KeyDB support.
|
||||
|
||||
New: Active Replica Support
|
||||
---------------------------
|
||||
|
||||
New! KeyDB now has support for Active Replicas. This feature greatly simplifies hot-spare failover and allows you to distribute writes over replicas instead of just a single master. For more information [see the wiki page](https://github.com/JohnSully/KeyDB/wiki/Active-Replication).
|
||||
|
||||
Why fork Redis?
|
||||
---------------
|
||||
|
||||
The Redis maintainers have continually reiterated that they do not plan to support multithreading. While we have great respect for the redis team, we feel the analysis justifying this decision is incorrect. In addition we wanted open source implementations of features currently only available in proprietary modules. We feel a fork is the best way to accelerate development in the areas of most interest to us.
|
||||
|
||||
We plan to track the Redis repo closely and hope our projects can learn from each other.
|
||||
|
||||
Benchmarking KeyDB
|
||||
------------------
|
||||
|
||||
<img src=https://cdn-images-1.medium.com/max/1400/1*s7mTb7Qb0kxc951mz8bdgA.png width=420 height=300/><img src=https://cdn-images-1.medium.com/max/1400/1*R00A5U4AFGohGOYHMfT6fA.png height=300/>
|
||||
|
||||
Please note keydb-benchmark and redis-benchmark are currently single threaded and too slow to properly benchmark KeyDB. We recommend using a redis cluster benchmark tool such as [memtier](https://github.com/RedisLabs/memtier_benchmark). Please ensure your machine has enough cores for both KeyDB and memteir if testing locally. KeyDB expects exclusive use of any cores assigned to it.
|
||||
|
||||
For more details on how we benchmarked KeyDB along with performance numbers check out our blog post: [Redis Should Be Multithreaded](https://medium.com/@john_63123/redis-should-be-multi-threaded-e28319cab744?source=friends_link&sk=7ce8e9fe3ec8224a4d27ef075d085457)
|
||||
@@ -58,6 +65,10 @@ If you would like to use the [FLASH backed](https://github.com/JohnSully/KeyDB/w
|
||||
|
||||
If you would like KeyDB to dump and load directly to AWS S3 this option specifies the bucket. Using this option with the traditional RDB options will result in KeyDB backing up twice to both locations. If both are specified KeyDB will first attempt to load from the local dump file and if that fails load from S3. This requires the AWS CLI tools to be installed and configured which are used under the hood to transfer the data.
|
||||
|
||||
active-replica yes
|
||||
|
||||
If you are using active-active replication set `active-replica` option to “yes”. This will enable both instances to accept reads and writes while remaining synced. [Click here](https://docs.keydb.dev/docs/active-rep/) to see more on active-rep in our docs section. There are also [docker examples]( https://docs.keydb.dev/docs/docker-active-rep/) on docs.
|
||||
|
||||
All other configuration options behave as you'd expect. Your existing configuration files should continue to work unchanged.
|
||||
|
||||
Building KeyDB
|
||||
@@ -67,16 +78,19 @@ KeyDB can be compiled and is tested for use on Linux. KeyDB currently relies on
|
||||
|
||||
Install dependencies:
|
||||
|
||||
% sudo apt install build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev
|
||||
% sudo apt install build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev libcurl4-openssl-dev
|
||||
|
||||
Compiling is as simple as:
|
||||
|
||||
% make
|
||||
|
||||
You can enable flash support with (Note: autoconf and autotools must be installed):
|
||||
You can enable flash support with:
|
||||
|
||||
% make MALLOC=memkind
|
||||
|
||||
***Note that the following dependencies may be needed:
|
||||
% sudo apt-get install autoconf autotools-dev libnuma-dev libtool
|
||||
|
||||
Fixing build problems with dependencies or cached build options
|
||||
---------
|
||||
|
||||
@@ -179,7 +193,7 @@ then in another terminal try the following:
|
||||
(integer) 2
|
||||
keydb>
|
||||
|
||||
You can find the list of all the available commands at http://redis.io/commands.
|
||||
You can find the list of all the available commands at https://docs.keydb.dev/docs/commands/
|
||||
|
||||
Installing KeyDB
|
||||
-----------------
|
||||
@@ -222,23 +236,18 @@ Future work:
|
||||
|
||||
Docker Build
|
||||
------------
|
||||
|
||||
Run the following commands for a full source download and build:
|
||||
|
||||
Build the latest binaries from the github unstable branch within a docker container. Note this is built for Ubuntu 18.04.
|
||||
Simply make a directory you would like to have the latest binaries dumped in, then run the following commmand with your updated path:
|
||||
```
|
||||
git clone git@github.com:JohnSully/KeyDB.git
|
||||
docker run -it --rm -v `pwd`/KeyDB:/build -w /build devopsdood/keydb-builder make
|
||||
$ docker run -it --rm -v /path-to-dump-binaries:/keydb_bin eqalpha/keydb-build-bin
|
||||
```
|
||||
You should receive the following files: keydb-benchmark, keydb-check-aof, keydb-check-rdb, keydb-cli, keydb-sentinel, keydb-server
|
||||
|
||||
Then you have fresh binaries built, you can also pass any other options to the make command above after the word make. E.g.
|
||||
|
||||
```docker run -it --rm -v `pwd`/KeyDB:/build -w /build devopsdood/keydb-builder make MAllOC=memkind```
|
||||
|
||||
The above commands will build you binaries in the src directory. Standard `make install` without Docker command will work after if you wish to install
|
||||
|
||||
If you'd prefer you can build the Dockerfile in the repo instead of pulling the above container for use:
|
||||
|
||||
`docker build -t KeyDB .`
|
||||
If you are looking to enable flash support with the build (make MALLOC=memkind) then use the following command:
|
||||
```
|
||||
$ docker run -it --rm -v /path-to-dump-binaries:/keydb_bin eqalpha/keydb-build-bin:flash
|
||||
```
|
||||
Please note that you will need libcurl4-openssl-dev in order to run keydb. With flash version you may need libnuma-dev and libtool installed in order to run the binaries. Keep this in mind especially when running in a container. For a copy of all our Dockerfiles, please see them on [docs]( https://docs.keydb.dev/docs/dockerfiles/).
|
||||
|
||||
Code contributions
|
||||
-----------------
|
||||
@@ -252,3 +261,4 @@ source distribution.
|
||||
Please see the CONTRIBUTING file in this source distribution for more
|
||||
information.
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
="repl-stream-db"
|
||||
="repl-id"
|
||||
="repl-offset"
|
||||
="lua"
|
||||
="redis-ver"
|
||||
="ctime"
|
||||
="used-mem"
|
||||
="aof-preamble"
|
||||
="redis-bits"
|
||||
="mvcc-tstamp"
|
||||
="keydb-subexpire-key"
|
||||
="keydb-subexpire-when"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
|
||||
class IStorage
|
||||
{
|
||||
public:
|
||||
typedef std::function<void(const char *, size_t, const void *, size_t)> callback;
|
||||
|
||||
virtual void insert(const char *key, size_t cchKey, void *data, size_t cb) = 0;
|
||||
virtual void erase(const char *key, size_t cchKey) = 0;
|
||||
virtual void retrieve(const char *key, size_t cchKey, bool fDelete, callback fn) = 0;
|
||||
virtual size_t clear() = 0;
|
||||
virtual void enumerate(callback fn) = 0;
|
||||
};
|
||||
+1
-1
@@ -209,7 +209,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=keydb-server
|
||||
REDIS_SENTINEL_NAME=keydb-sentinel
|
||||
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 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 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 $(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 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 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 $(ASM_OBJ)
|
||||
REDIS_CLI_NAME=keydb-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o $(ASM_OBJ)
|
||||
REDIS_BENCHMARK_NAME=keydb-benchmark
|
||||
|
||||
+11
-11
@@ -295,7 +295,7 @@ int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
|
||||
* Note that this function does not check the ALLCOMMANDS flag of the user
|
||||
* but just the lowlevel bitmask.
|
||||
*
|
||||
* If the bit overflows the user internal represetation, zero is returned
|
||||
* If the bit overflows the user internal representation, zero is returned
|
||||
* in order to disallow the execution of the command in such edge case. */
|
||||
int ACLGetUserCommandBit(user *u, unsigned long id) {
|
||||
uint64_t word, bit;
|
||||
@@ -311,7 +311,7 @@ int ACLUserCanExecuteFutureCommands(user *u) {
|
||||
}
|
||||
|
||||
/* Set the specified command bit for the specified user to 'value' (0 or 1).
|
||||
* If the bit overflows the user internal represetation, no operation
|
||||
* If the bit overflows the user internal representation, no operation
|
||||
* is performed. As a side effect of calling this function with a value of
|
||||
* zero, the user flag ALLCOMMANDS is cleared since it is no longer possible
|
||||
* to skip the command bit explicit test. */
|
||||
@@ -350,7 +350,7 @@ int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) {
|
||||
|
||||
/* Return the number of commands allowed (on) and denied (off) for the user 'u'
|
||||
* in the subset of commands flagged with the specified category name.
|
||||
* If the categoty name is not valid, C_ERR is returend, otherwise C_OK is
|
||||
* If the category name is not valid, C_ERR is returned, otherwise C_OK is
|
||||
* returned and on and off are populated by reference. */
|
||||
int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off,
|
||||
const char *category)
|
||||
@@ -626,7 +626,7 @@ 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.
|
||||
* ><password> Add this passowrd to the list of valid password for the user.
|
||||
* ><password> Add this password to the list of valid password for the user.
|
||||
* For example >mypass will add "mypass" to the list.
|
||||
* This directive clears the "nopass" flag (see later).
|
||||
* <<password> Remove this password from the list of valid passwords.
|
||||
@@ -949,9 +949,9 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
return (user*)myuser;
|
||||
}
|
||||
|
||||
/* Check if the command ready to be excuted in the client 'c', and already
|
||||
* referenced by c->cmd, can be executed by this client according to the
|
||||
* ACls associated to the client user c->user.
|
||||
/* 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->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
|
||||
@@ -1122,7 +1122,7 @@ int ACLLoadConfiguredUsers(void) {
|
||||
}
|
||||
|
||||
/* This function loads the ACL from the specified filename: every line
|
||||
* is validated and shold be either empty or in the format used to specify
|
||||
* is validated and should be either empty or in the format used to specify
|
||||
* users in the redis.conf configuration or in the ACL file, that is:
|
||||
*
|
||||
* user <username> ... rules ...
|
||||
@@ -1172,7 +1172,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
* to the real user mentioned in the ACL line. */
|
||||
user *fakeuser = ACLCreateUnlinkedUser();
|
||||
|
||||
/* We do all the loading in a fresh insteance of the Users radix tree,
|
||||
/* We do all the loading in a fresh instance of the Users radix tree,
|
||||
* so if there are errors loading the ACL file we can rollback to the
|
||||
* old version. */
|
||||
rax *old_users = Users;
|
||||
@@ -1248,7 +1248,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
}
|
||||
|
||||
/* Note that the same rules already applied to the fake user, so
|
||||
* we just assert that everything goess well: it should. */
|
||||
* we just assert that everything goes well: it should. */
|
||||
for (j = 2; j < argc; j++)
|
||||
serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK);
|
||||
|
||||
@@ -1611,7 +1611,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
|
||||
setDeferredSetLen(c, flaglen, flagcount);
|
||||
}
|
||||
|
||||
/* AUTH <passowrd>
|
||||
/* AUTH <password>
|
||||
* AUTH <username> <password> (Redis >= 6.0 form)
|
||||
*
|
||||
* When the user is omitted it means that we are trying to authenticate
|
||||
|
||||
+32
-1
@@ -191,6 +191,36 @@ void aeProcessCmd(aeEventLoop *eventLoop, int fd, void *, int )
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike write() this is an all or nothing thing. We will block if a partial write is hit
|
||||
ssize_t safe_write(int fd, const void *pv, size_t cb)
|
||||
{
|
||||
const char *pcb = (const char*)pv;
|
||||
ssize_t written = 0;
|
||||
do
|
||||
{
|
||||
ssize_t rval = write(fd, pcb, cb);
|
||||
if (rval > 0)
|
||||
{
|
||||
pcb += rval;
|
||||
cb -= rval;
|
||||
written += rval;
|
||||
}
|
||||
else if (errno == EAGAIN)
|
||||
{
|
||||
if (written == 0)
|
||||
break;
|
||||
// if we've already written something then we're committed so keep trying
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rval == 0)
|
||||
return written;
|
||||
return rval;
|
||||
}
|
||||
} while (cb);
|
||||
return written;
|
||||
}
|
||||
|
||||
int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
aeFileProc *proc, void *clientData, int fSynchronous)
|
||||
{
|
||||
@@ -212,9 +242,10 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
if (fSynchronous)
|
||||
cmd.pctl->mutexcv.lock();
|
||||
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
auto size = safe_write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
if (size != sizeof(cmd))
|
||||
{
|
||||
AE_ASSERT(size == sizeof(cmd) || size <= 0);
|
||||
AE_ASSERT(errno == EAGAIN);
|
||||
ret = AE_ERR;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,11 @@ public:
|
||||
return m_fArmed;
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
m_fArmed = false;
|
||||
}
|
||||
|
||||
~AeLocker()
|
||||
{
|
||||
if (m_fArmed)
|
||||
|
||||
+11
-7
@@ -476,7 +476,7 @@ static int anetV6Only(char *err, int s) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort)
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort, int fFirstListen)
|
||||
{
|
||||
int s = -1, rv;
|
||||
char _port[6]; /* strlen("65535") */
|
||||
@@ -498,8 +498,12 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
|
||||
|
||||
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (fReusePort && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
|
||||
if (fReusePort && !fFirstListen && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) {
|
||||
s = ANET_ERR;
|
||||
goto end;
|
||||
}
|
||||
if (fReusePort && fFirstListen && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
@@ -515,14 +519,14 @@ end:
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort)
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort, fFirstListen);
|
||||
}
|
||||
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort)
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort, fFirstListen);
|
||||
}
|
||||
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
|
||||
|
||||
+2
-2
@@ -62,8 +62,8 @@ int anetUnixNonBlockConnect(char *err, 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 anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort);
|
||||
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 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);
|
||||
|
||||
+62
-45
@@ -97,6 +97,7 @@ void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
aofrwblock *block;
|
||||
ssize_t nwritten;
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
serverAssert(el == g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el); // MUST run on main thread
|
||||
|
||||
UNUSED(el);
|
||||
UNUSED(fd);
|
||||
@@ -164,10 +165,7 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
|
||||
/* Install a file event to send data to the rewrite child if there is
|
||||
* not one already. */
|
||||
if (aeGetFileEvents(serverTL->el,g_pserver->aof_pipe_write_data_to_child) == 0) {
|
||||
aeCreateFileEvent(serverTL->el, g_pserver->aof_pipe_write_data_to_child,
|
||||
AE_WRITABLE, aofChildWriteDiffData, NULL);
|
||||
}
|
||||
aeCreateRemoteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->aof_pipe_write_data_to_child, AE_WRITABLE, aofChildWriteDiffData, NULL, FALSE);
|
||||
}
|
||||
|
||||
/* Write the buffer (possibly composed of multiple blocks) into the specified
|
||||
@@ -266,7 +264,7 @@ int startAppendOnly(void) {
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
}
|
||||
if (g_pserver->rdb_child_pid != -1) {
|
||||
if (g_pserver->FRdbSaveInProgress()) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
||||
} else {
|
||||
@@ -399,7 +397,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* useful for graphing / monitoring purposes. */
|
||||
if (sync_in_progress) {
|
||||
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
||||
} else if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) {
|
||||
} else if (g_pserver->aof_child_pid != -1 || g_pserver->FRdbSaveInProgress()) {
|
||||
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
||||
} else {
|
||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||
@@ -496,7 +494,7 @@ try_fsync:
|
||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||
* children doing I/O in the background. */
|
||||
if (g_pserver->aof_no_fsync_on_rewrite &&
|
||||
(g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1))
|
||||
(g_pserver->aof_child_pid != -1 || g_pserver->FRdbSaveInProgress()))
|
||||
return;
|
||||
|
||||
/* Perform the fsync if needed. */
|
||||
@@ -668,7 +666,7 @@ client *createFakeClient(void) {
|
||||
c->flags = 0;
|
||||
c->fPendingAsyncWrite = FALSE;
|
||||
c->btype = BLOCKED_NONE;
|
||||
/* We set the fake client as a slave waiting for the synchronization
|
||||
/* We set the fake client as a replica waiting for the synchronization
|
||||
* so that Redis will not try to send replies to this client. */
|
||||
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
||||
c->reply = listCreate();
|
||||
@@ -1302,71 +1300,77 @@ ssize_t aofReadDiffFromParent(void) {
|
||||
|
||||
int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
size_t processed = 0;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||
redisDb *db = g_pserver->db+j;
|
||||
dict *d = db->pdict;
|
||||
if (dictSize(d) == 0) continue;
|
||||
di = dictGetSafeIterator(d);
|
||||
if (db->size() == 0) continue;
|
||||
|
||||
/* SELECT the new DB */
|
||||
if (rioWrite(aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkLongLong(aof,j) == 0) goto werr;
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr;
|
||||
robj key, *o;
|
||||
long long expiretime;
|
||||
bool fComplete = db->iterate([&](const char *keystr, robj *o)->bool{
|
||||
robj key;
|
||||
|
||||
keystr = (sds)dictGetKey(de);
|
||||
o = (robj*)dictGetVal(de);
|
||||
initStaticStringObject(key,keystr);
|
||||
initStaticStringObject(key,(sds)keystr);
|
||||
|
||||
expiretime = getExpire(db,&key);
|
||||
expireEntry *pexpire = db->getExpire(&key);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
/* Emit a SET command */
|
||||
char cmd[]="*3\r\n$3\r\nSET\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) return false;
|
||||
/* Key and value */
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,o) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) return false;
|
||||
if (rioWriteBulkObject(aof,o) == 0) return false;
|
||||
} else if (o->type == OBJ_LIST) {
|
||||
if (rewriteListObject(aof,&key,o) == 0) goto werr;
|
||||
if (rewriteListObject(aof,&key,o) == 0) return false;
|
||||
} else if (o->type == OBJ_SET) {
|
||||
if (rewriteSetObject(aof,&key,o) == 0) goto werr;
|
||||
if (rewriteSetObject(aof,&key,o) == 0) return false;
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
if (rewriteSortedSetObject(aof,&key,o) == 0) goto werr;
|
||||
if (rewriteSortedSetObject(aof,&key,o) == 0) return false;
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
if (rewriteHashObject(aof,&key,o) == 0) goto werr;
|
||||
if (rewriteHashObject(aof,&key,o) == 0) return false;
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
if (rewriteStreamObject(aof,&key,o) == 0) goto werr;
|
||||
if (rewriteStreamObject(aof,&key,o) == 0) return false;
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
if (rewriteModuleObject(aof,&key,o) == 0) goto werr;
|
||||
if (rewriteModuleObject(aof,&key,o) == 0) return false;
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
if (rioWriteBulkLongLong(aof,expiretime) == 0) goto werr;
|
||||
if (pexpire != nullptr) {
|
||||
for (auto &subExpire : *pexpire) {
|
||||
if (subExpire.subkey() == nullptr)
|
||||
{
|
||||
char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) return false;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
char cmd[]="*4\r\n$12\r\nEXPIREMEMBER\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) return false;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) return false;
|
||||
if (rioWrite(aof,subExpire.subkey(),sdslen(subExpire.subkey())) == 0) return false;
|
||||
}
|
||||
if (rioWriteBulkLongLong(aof,subExpire.when()) == 0) return false; // common
|
||||
}
|
||||
}
|
||||
/* Read some diff from the parent process from time to time. */
|
||||
if (aof->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES) {
|
||||
processed = aof->processed_bytes;
|
||||
aofReadDiffFromParent();
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
di = NULL;
|
||||
return true;
|
||||
});
|
||||
if (!fComplete)
|
||||
goto werr;
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
@@ -1407,7 +1411,12 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
|
||||
if (g_pserver->aof_use_rdb_preamble) {
|
||||
int error;
|
||||
if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
std::vector<redisDbPersistentData*> vecpdb;
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
{
|
||||
vecpdb.push_back(&g_pserver->db[idb]);
|
||||
}
|
||||
if (rdbSaveRio(&aof,vecpdb.data(),&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
errno = error;
|
||||
goto werr;
|
||||
}
|
||||
@@ -1508,7 +1517,7 @@ void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
/* Remove the handler since this can be called only one time during a
|
||||
* rewrite. */
|
||||
aeDeleteFileEventAsync(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
aeDeleteFileEvent(el,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
}
|
||||
|
||||
/* Create the pipes used for parent - child process IPC during rewrite.
|
||||
@@ -1546,12 +1555,20 @@ error:
|
||||
}
|
||||
|
||||
void aofClosePipes(void) {
|
||||
aeDeleteFileEventAsync(g_pserver->el_alf_pip_read_ack_from_child,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
aeDeleteFileEventAsync(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,g_pserver->aof_pipe_write_data_to_child,AE_WRITABLE);
|
||||
close(g_pserver->aof_pipe_write_data_to_child);
|
||||
int fdAofAckPipe = g_pserver->aof_pipe_read_ack_from_child;
|
||||
aePostFunction(g_pserver->el_alf_pip_read_ack_from_child, [fdAofAckPipe]{
|
||||
aeDeleteFileEventAsync(serverTL->el,fdAofAckPipe,AE_READABLE);
|
||||
close (fdAofAckPipe);
|
||||
});
|
||||
|
||||
int fdAofWritePipe = g_pserver->aof_pipe_write_data_to_child;
|
||||
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fdAofWritePipe]{
|
||||
aeDeleteFileEventAsync(serverTL->el,fdAofWritePipe,AE_WRITABLE);
|
||||
close(fdAofWritePipe);
|
||||
});
|
||||
|
||||
close(g_pserver->aof_pipe_read_data_from_parent);
|
||||
close(g_pserver->aof_pipe_write_ack_to_parent);
|
||||
close(g_pserver->aof_pipe_read_ack_from_child);
|
||||
close(g_pserver->aof_pipe_write_ack_to_child);
|
||||
close(g_pserver->aof_pipe_read_ack_from_parent);
|
||||
}
|
||||
@@ -1576,7 +1593,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) return C_ERR;
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->FRdbSaveInProgress()) return C_ERR;
|
||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||
openChildInfoPipe();
|
||||
start = ustime();
|
||||
@@ -1635,7 +1652,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
void bgrewriteaofCommand(client *c) {
|
||||
if (g_pserver->aof_child_pid != -1) {
|
||||
addReplyError(c,"Background append only file rewriting already in progress");
|
||||
} else if (g_pserver->rdb_child_pid != -1) {
|
||||
} else if (g_pserver->FRdbSaveInProgress()) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
||||
|
||||
+2
-2
@@ -85,7 +85,7 @@ struct bio_job {
|
||||
|
||||
void *bioProcessBackgroundJobs(void *arg);
|
||||
void lazyfreeFreeObjectFromBioThread(robj *o);
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2);
|
||||
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
|
||||
@@ -196,7 +196,7 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
if (job->arg1)
|
||||
lazyfreeFreeObjectFromBioThread((robj*)job->arg1);
|
||||
else if (job->arg2 && job->arg3)
|
||||
lazyfreeFreeDatabaseFromBioThread((dict*)job->arg2,(dict*)job->arg3);
|
||||
lazyfreeFreeDatabaseFromBioThread((dict*)job->arg2,(expireset*)job->arg3);
|
||||
else if (job->arg3)
|
||||
lazyfreeFreeSlotsMapFromBioThread((rax*)job->arg3);
|
||||
} else {
|
||||
|
||||
+154
-81
@@ -396,6 +396,8 @@ void printBits(unsigned char *p, unsigned long count) {
|
||||
#define BITOP_OR 1
|
||||
#define BITOP_XOR 2
|
||||
#define BITOP_NOT 3
|
||||
#define BITOP_LSHIFT 4
|
||||
#define BITOP_RSHIFT 5
|
||||
|
||||
#define BITFIELDOP_GET 0
|
||||
#define BITFIELDOP_SET 1
|
||||
@@ -592,7 +594,8 @@ void bitopCommand(client *c) {
|
||||
char *opname = szFromObj(c->argv[1]);
|
||||
robj *targetkey = c->argv[2];
|
||||
robj_roptr o;
|
||||
unsigned long op, j, numkeys;
|
||||
int op;
|
||||
unsigned long j, numkeys;
|
||||
robj_roptr *objects; /* Array of source objects. */
|
||||
unsigned char **src; /* Array of source strings pointers. */
|
||||
unsigned long *len, maxlen = 0; /* Array of length of src strings,
|
||||
@@ -609,6 +612,10 @@ void bitopCommand(client *c) {
|
||||
op = BITOP_XOR;
|
||||
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
|
||||
op = BITOP_NOT;
|
||||
else if (!strcasecmp(opname, "lshift"))
|
||||
op = BITOP_LSHIFT;
|
||||
else if (!strcasecmp(opname, "rshift"))
|
||||
op = BITOP_RSHIFT;
|
||||
else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
@@ -620,8 +627,25 @@ void bitopCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool fShiftOp = (op == BITOP_LSHIFT) || (op == BITOP_RSHIFT);
|
||||
long long shift = 0;
|
||||
|
||||
/* Sanity check: SHIFTS only accept a single arg and an integer */
|
||||
if (fShiftOp) {
|
||||
if (c->argc != 5) {
|
||||
addReplyError(c,"BITOP SHIFT must be called with a single source key and an integer shift.");
|
||||
return;
|
||||
}
|
||||
if (getLongLongFromObject(c->argv[4], &shift) != C_OK) {
|
||||
addReplyError(c, "BITOP SHIFT's last parameter must be an integer");
|
||||
return;
|
||||
}
|
||||
if (op == BITOP_RSHIFT)
|
||||
shift = -shift;
|
||||
}
|
||||
|
||||
/* Lookup keys, and store pointers to the string objects into an array. */
|
||||
numkeys = c->argc - 3;
|
||||
numkeys = c->argc - (fShiftOp ? 4 : 3);
|
||||
src = (unsigned char**)zmalloc(sizeof(unsigned char*) * numkeys, MALLOC_LOCAL);
|
||||
len = (unsigned long*)zmalloc(sizeof(long) * numkeys, MALLOC_LOCAL);
|
||||
objects = (robj_roptr*)zmalloc(sizeof(robj_roptr) * numkeys, MALLOC_LOCAL);
|
||||
@@ -654,94 +678,143 @@ void bitopCommand(client *c) {
|
||||
if (j == 0 || len[j] < minlen) minlen = len[j];
|
||||
}
|
||||
|
||||
/* Compute the bit operation, if at least one string is not empty. */
|
||||
if (maxlen) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte;
|
||||
unsigned long i;
|
||||
if (fShiftOp)
|
||||
{
|
||||
long newlen = (long)maxlen + shift/CHAR_BIT;
|
||||
if (shift > 0 && (shift % CHAR_BIT) != 0)
|
||||
newlen++;
|
||||
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. On ARM we skip the fast path since it will
|
||||
* result in GCC compiling the code using multiple-words load/store
|
||||
* operations that are not supported even in ARM >= v6. */
|
||||
j = 0;
|
||||
#ifndef USE_ALIGNED_ACCESS
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
if (newlen < 0)
|
||||
newlen = 0;
|
||||
|
||||
if (newlen)
|
||||
{
|
||||
res = (unsigned char*) sdsnewlen(NULL,newlen);
|
||||
if (shift >= 0)
|
||||
{ // left shift
|
||||
long byteoffset = shift/CHAR_BIT;
|
||||
memset(res, 0, byteoffset);
|
||||
long srcLen = newlen - byteoffset - ((shift % CHAR_BIT) ? 1 : 0);
|
||||
|
||||
/* Note: sds pointer is always aligned to 8 byte boundary. */
|
||||
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
|
||||
memcpy(res,src[0],minlen);
|
||||
|
||||
/* Different branches per different operations for speed (sorry). */
|
||||
if (op == BITOP_AND) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] &= lp[i][0];
|
||||
lres[1] &= lp[i][1];
|
||||
lres[2] &= lp[i][2];
|
||||
lres[3] &= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
// now the bitshift+copy
|
||||
unsigned bitshift = shift % CHAR_BIT;
|
||||
unsigned char carry = 0;
|
||||
for (long iSrc = 0; iSrc < srcLen; ++iSrc)
|
||||
{
|
||||
res[byteoffset+iSrc] = (src[0][iSrc] << bitshift) | carry;
|
||||
carry = src[0][iSrc] >> (CHAR_BIT - bitshift);
|
||||
}
|
||||
} else if (op == BITOP_OR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] |= lp[i][0];
|
||||
lres[1] |= lp[i][1];
|
||||
lres[2] |= lp[i][2];
|
||||
lres[3] |= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_XOR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] ^= lp[i][0];
|
||||
lres[1] ^= lp[i][1];
|
||||
lres[2] ^= lp[i][2];
|
||||
lres[3] ^= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_NOT) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
lres[0] = ~lres[0];
|
||||
lres[1] = ~lres[1];
|
||||
lres[2] = ~lres[2];
|
||||
lres[3] = ~lres[3];
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
if (bitshift)
|
||||
res[newlen-1] = carry;
|
||||
}
|
||||
else
|
||||
{ // right shift
|
||||
long byteoffset = -shift/CHAR_BIT;
|
||||
unsigned bitshift = -shift % CHAR_BIT;
|
||||
if (bitshift)
|
||||
++byteoffset;
|
||||
res[0] = (src[0][byteoffset] << (CHAR_BIT-bitshift));
|
||||
if (byteoffset > 0)
|
||||
res[0] |= (src[0][byteoffset-1] >> bitshift);
|
||||
for (long idx = 1; idx < newlen; ++idx)
|
||||
{
|
||||
res[idx] = (src[0][byteoffset+idx] << (CHAR_BIT-bitshift)) | (src[0][byteoffset+idx-1] >> bitshift);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
maxlen = newlen; // this is to ensure we DEL below if newlen was 0
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 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;
|
||||
|
||||
/* 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;
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. On ARM we skip the fast path since it will
|
||||
* result in GCC compiling the code using multiple-words load/store
|
||||
* operations that are not supported even in ARM >= v6. */
|
||||
j = 0;
|
||||
#ifndef USE_ALIGNED_ACCESS
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
|
||||
/* Note: sds pointer is always aligned to 8 byte boundary. */
|
||||
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
|
||||
memcpy(res,src[0],minlen);
|
||||
|
||||
/* Different branches per different operations for speed (sorry). */
|
||||
if (op == BITOP_AND) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] &= lp[i][0];
|
||||
lres[1] &= lp[i][1];
|
||||
lres[2] &= lp[i][2];
|
||||
lres[3] &= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_OR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] |= lp[i][0];
|
||||
lres[1] |= lp[i][1];
|
||||
lres[2] |= lp[i][2];
|
||||
lres[3] |= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_XOR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] ^= lp[i][0];
|
||||
lres[1] ^= lp[i][1];
|
||||
lres[2] ^= lp[i][2];
|
||||
lres[3] ^= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_NOT) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
lres[0] = ~lres[0];
|
||||
lres[1] = ~lres[1];
|
||||
lres[2] = ~lres[2];
|
||||
lres[3] = ~lres[3];
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
}
|
||||
}
|
||||
res[j] = output;
|
||||
#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;
|
||||
}
|
||||
}
|
||||
res[j] = output;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
|
||||
+2
-2
@@ -221,8 +221,8 @@ void replyToBlockedClientTimedOut(client *c) {
|
||||
|
||||
/* Mass-unblock clients because something changed in the instance that makes
|
||||
* blocking no longer safe. For example clients blocked in list operations
|
||||
* in an instance which turns from master to slave is unsafe, so this function
|
||||
* is called when a master turns into a slave.
|
||||
* in an instance which turns from master to replica is unsafe, so this function
|
||||
* is called when a master turns into a replica.
|
||||
*
|
||||
* The semantics is to send an -UNBLOCKED error to the client, disconnecting
|
||||
* it at the same time. */
|
||||
|
||||
+29
-7
@@ -499,7 +499,7 @@ void clusterInit(void) {
|
||||
}
|
||||
|
||||
if (listenToPort(g_pserver->port+CLUSTER_PORT_INCR,
|
||||
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/) == C_ERR)
|
||||
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR)
|
||||
{
|
||||
exit(1);
|
||||
} else {
|
||||
@@ -3910,7 +3910,7 @@ int verifyClusterConfigWithData(void) {
|
||||
|
||||
/* Make sure we only have keys in DB0. */
|
||||
for (j = 1; j < cserver.dbnum; j++) {
|
||||
if (dictSize(g_pserver->db[j].pdict)) return C_ERR;
|
||||
if (g_pserver->db[j].size()) return C_ERR;
|
||||
}
|
||||
|
||||
/* Check that all the slots we see populated memory have a corresponding
|
||||
@@ -4286,7 +4286,7 @@ NULL
|
||||
clusterReplyMultiBulkSlots(c);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"flushslots") && c->argc == 2) {
|
||||
/* CLUSTER FLUSHSLOTS */
|
||||
if (dictSize(g_pserver->db[0].pdict) != 0) {
|
||||
if (g_pserver->db[0].size() != 0) {
|
||||
addReplyError(c,"DB must be empty to perform CLUSTER FLUSHSLOTS.");
|
||||
return;
|
||||
}
|
||||
@@ -4621,7 +4621,7 @@ NULL
|
||||
* slots nor keys to accept to replicate some other node.
|
||||
* Slaves can switch to another master without issues. */
|
||||
if (nodeIsMaster(myself) &&
|
||||
(myself->numslots != 0 || dictSize(g_pserver->db[0].pdict) != 0)) {
|
||||
(myself->numslots != 0 || g_pserver->db[0].size() != 0)) {
|
||||
addReplyError(c,
|
||||
"To set a master the node must be empty and "
|
||||
"without assigned slots.");
|
||||
@@ -4778,7 +4778,7 @@ NULL
|
||||
|
||||
/* Slaves can be reset while containing data, but not master nodes
|
||||
* that must be empty. */
|
||||
if (nodeIsMaster(myself) && dictSize(c->db->pdict) != 0) {
|
||||
if (nodeIsMaster(myself) && c->db->size() != 0) {
|
||||
addReplyError(c,"CLUSTER RESET can't be called with "
|
||||
"master nodes containing keys");
|
||||
return;
|
||||
@@ -4794,6 +4794,7 @@ NULL
|
||||
/* -----------------------------------------------------------------------------
|
||||
* DUMP, RESTORE and MIGRATE commands
|
||||
* -------------------------------------------------------------------------- */
|
||||
ssize_t rdbSaveAuxFieldStrStr(rio *rdb, const char *key, const char *val);
|
||||
|
||||
/* Generates a DUMP-format representation of the object 'o', adding it to the
|
||||
* io stream pointed by 'rio'. This function can't fail. */
|
||||
@@ -4806,6 +4807,9 @@ void createDumpPayload(rio *payload, robj_roptr o, robj *key) {
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
serverAssert(rdbSaveObjectType(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o,key));
|
||||
char szT[32];
|
||||
snprintf(szT, 32, "%" PRIu64, o->mvcc_tstamp);
|
||||
serverAssert(rdbSaveAuxFieldStrStr(payload,"mvcc-tstamp", szT) != -1);
|
||||
|
||||
/* Write the footer, this is how it looks like:
|
||||
* ----------------+---------------------+---------------+
|
||||
@@ -4941,6 +4945,21 @@ void restoreCommand(client *c) {
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
}
|
||||
if (rdbLoadType(&payload) == RDB_OPCODE_AUX)
|
||||
{
|
||||
robj *auxkey, *auxval;
|
||||
if ((auxkey = rdbLoadStringObject(&payload)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(&payload)) == NULL) {
|
||||
decrRefCount(auxkey);
|
||||
goto eoferr;
|
||||
}
|
||||
if (strcasecmp(szFromObj(auxkey), "mvcc-tstamp") == 0) {
|
||||
obj->mvcc_tstamp = strtoull(szFromObj(auxval), nullptr, 10);
|
||||
}
|
||||
decrRefCount(auxkey);
|
||||
decrRefCount(auxval);
|
||||
}
|
||||
eoferr:
|
||||
|
||||
/* Remove the old key if needed. */
|
||||
if (replace) dbDelete(c->db,c->argv[1]);
|
||||
@@ -4949,7 +4968,7 @@ void restoreCommand(client *c) {
|
||||
dbAdd(c->db,c->argv[1],obj);
|
||||
if (ttl) {
|
||||
if (!absttl) ttl+=mstime();
|
||||
setExpire(c,c->db,c->argv[1],ttl);
|
||||
setExpire(c,c->db,c->argv[1],nullptr,ttl);
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
@@ -5194,7 +5213,10 @@ try_again:
|
||||
/* Create RESTORE payload and generate the protocol to call the command. */
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
long long ttl = 0;
|
||||
long long expireat = getExpire(c->db,kv[j]);
|
||||
expireEntry *pexpire = c->db->getExpire(kv[j]);
|
||||
long long expireat = -1;
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expireat);
|
||||
|
||||
if (expireat != -1) {
|
||||
ttl = expireat-mstime();
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <assert.h>
|
||||
|
||||
/*************************************************
|
||||
* compactvector - similar to std::vector but optimized for minimal memory
|
||||
*
|
||||
* Notable differences:
|
||||
* - Limited to 2^32 elements
|
||||
* - Grows linearly not exponentially
|
||||
*
|
||||
*************************************************/
|
||||
|
||||
template<typename T, bool MEMMOVE_SAFE=false>
|
||||
class compactvector
|
||||
{
|
||||
static_assert(MEMMOVE_SAFE || std::is_trivially_copyable<T>::value, "compactvector requires trivially copyable types");
|
||||
T *m_data = nullptr;
|
||||
unsigned m_celem = 0;
|
||||
unsigned m_max = 0;
|
||||
|
||||
public:
|
||||
typedef T* iterator;
|
||||
|
||||
compactvector() noexcept = default;
|
||||
~compactvector() noexcept
|
||||
{
|
||||
clear(); // call dtors
|
||||
zfree(m_data);
|
||||
}
|
||||
|
||||
compactvector(const compactvector &src)
|
||||
{
|
||||
m_celem = src.m_celem;
|
||||
m_max = src.m_max;
|
||||
m_data = (T*)zmalloc(sizeof(T) * m_max, MALLOC_LOCAL);
|
||||
for (size_t ielem = 0; ielem < m_celem; ++ielem)
|
||||
{
|
||||
new (m_data+ielem) T(src[ielem]);
|
||||
}
|
||||
}
|
||||
|
||||
compactvector(compactvector &&src) noexcept
|
||||
{
|
||||
m_data = src.m_data;
|
||||
m_celem = src.m_celem;
|
||||
m_max = src.m_max;
|
||||
src.m_data = nullptr;
|
||||
src.m_celem = 0;
|
||||
src.m_max = 0;
|
||||
}
|
||||
|
||||
compactvector &operator=(compactvector &&src) noexcept
|
||||
{
|
||||
zfree(m_data);
|
||||
m_data = src.m_data;
|
||||
m_celem = src.m_celem;
|
||||
m_max = src.m_max;
|
||||
src.m_data = nullptr;
|
||||
src.m_celem = 0;
|
||||
src.m_max = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline T* begin() { return m_data; }
|
||||
inline const T* begin() const { return m_data; }
|
||||
|
||||
inline T* end() { return m_data + m_celem; }
|
||||
inline const T* end() const { return m_data + m_celem; }
|
||||
|
||||
T* insert(T* where, T &val)
|
||||
{
|
||||
assert(where >= m_data);
|
||||
size_t idx = where - m_data;
|
||||
if (m_celem >= m_max)
|
||||
{
|
||||
if (m_max < 2)
|
||||
m_max = 2;
|
||||
else
|
||||
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);
|
||||
}
|
||||
assert(idx < m_max);
|
||||
where = m_data + idx;
|
||||
memmove(reinterpret_cast<void*>(m_data + idx + 1), reinterpret_cast<const void*>(m_data + idx), (m_celem - idx)*sizeof(T));
|
||||
new(m_data + idx) T(std::move(val));
|
||||
++m_celem;
|
||||
return where;
|
||||
}
|
||||
|
||||
T &operator[](size_t idx)
|
||||
{
|
||||
assert(idx < m_celem);
|
||||
return m_data[idx];
|
||||
}
|
||||
const T &operator[](size_t idx) const
|
||||
{
|
||||
assert(idx < m_celem);
|
||||
return m_data[idx];
|
||||
}
|
||||
|
||||
T& back() { assert(m_celem > 0); return m_data[m_celem-1]; }
|
||||
const T& back() const { assert(m_celem > 0); return m_data[m_celem-1]; }
|
||||
|
||||
void erase(T* where)
|
||||
{
|
||||
assert(where >= m_data);
|
||||
size_t idx = where - m_data;
|
||||
assert(idx < m_celem);
|
||||
where->~T();
|
||||
memmove(reinterpret_cast<void*>(where), reinterpret_cast<const void*>(where+1), ((m_celem - idx - 1)*sizeof(T)));
|
||||
--m_celem;
|
||||
|
||||
if (m_celem == 0)
|
||||
{
|
||||
zfree(m_data);
|
||||
m_data = nullptr;
|
||||
m_max = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void shrink_to_fit()
|
||||
{
|
||||
if (m_max == m_celem)
|
||||
return;
|
||||
m_data = (T*)zrealloc(m_data, sizeof(T) * m_celem, MALLOC_LOCAL);
|
||||
m_max = m_celem; // NOTE: We do not get the usable size here, because this could cause us to continually realloc
|
||||
}
|
||||
|
||||
size_t bytes_used() const
|
||||
{
|
||||
return sizeof(this) + (m_max * sizeof(T));
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (size_t idx = 0; idx < m_celem; ++idx)
|
||||
m_data[idx].~T();
|
||||
zfree(m_data);
|
||||
m_data = nullptr;
|
||||
m_celem = 0;
|
||||
m_max = 0;
|
||||
}
|
||||
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return m_celem == 0;
|
||||
}
|
||||
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return m_celem;
|
||||
}
|
||||
|
||||
T* data() noexcept { return m_data; }
|
||||
const T* data() const noexcept { return m_data; }
|
||||
};
|
||||
static_assert(sizeof(compactvector<void*>) <= 16, "not compact");
|
||||
+90
-257
@@ -96,10 +96,51 @@ configEnum aof_fsync_enum[] = {
|
||||
/* Output buffer limits presets. */
|
||||
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
|
||||
{0, 0, 0}, /* normal */
|
||||
{1024*1024*256, 1024*1024*64, 60}, /* slave */
|
||||
{1024*1024*256, 1024*1024*64, 60}, /* replica */
|
||||
{1024*1024*32, 1024*1024*8, 60} /* pubsub */
|
||||
};
|
||||
|
||||
/* Configuration values that require no special handling to set, get, load or
|
||||
* rewrite. */
|
||||
typedef struct configYesNo {
|
||||
const char *name; /* The user visible name of this config */
|
||||
const char *alias; /* An alias that can also be used for this config */
|
||||
int *config; /* The pointer to the server config this value is stored in */
|
||||
const int modifiable; /* Can this value be updated by CONFIG SET? */
|
||||
const int default_value; /* The default value of the config on rewrite */
|
||||
} configYesNo;
|
||||
|
||||
configYesNo configs_yesno[] = {
|
||||
/* Non-Modifiable */
|
||||
{"rdbchecksum",NULL,&g_pserver->rdb_checksum,0,CONFIG_DEFAULT_RDB_CHECKSUM},
|
||||
{"daemonize",NULL,&cserver.daemonize,0,0},
|
||||
{"always-show-logo",NULL,&g_pserver->always_show_logo,0,CONFIG_DEFAULT_ALWAYS_SHOW_LOGO},
|
||||
/* Modifiable */
|
||||
{"protected-mode",NULL,&g_pserver->protected_mode,1,CONFIG_DEFAULT_PROTECTED_MODE},
|
||||
{"rdbcompression",NULL,&g_pserver->rdb_compression,1,CONFIG_DEFAULT_RDB_COMPRESSION},
|
||||
{"activerehashing",NULL,&g_pserver->activerehashing,1,CONFIG_DEFAULT_ACTIVE_REHASHING},
|
||||
{"stop-writes-on-bgsave-error",NULL,&g_pserver->stop_writes_on_bgsave_err,1,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR},
|
||||
{"dynamic-hz",NULL,&g_pserver->dynamic_hz,1,CONFIG_DEFAULT_DYNAMIC_HZ},
|
||||
{"lazyfree-lazy-eviction",NULL,&g_pserver->lazyfree_lazy_eviction,1,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION},
|
||||
{"lazyfree-lazy-expire",NULL,&g_pserver->lazyfree_lazy_expire,1,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE},
|
||||
{"lazyfree-lazy-server-del",NULL,&g_pserver->lazyfree_lazy_server_del,1,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL},
|
||||
{"repl-disable-tcp-nodelay",NULL,&g_pserver->repl_disable_tcp_nodelay,1,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY},
|
||||
{"repl-diskless-sync",NULL,&g_pserver->repl_diskless_sync,1,CONFIG_DEFAULT_REPL_DISKLESS_SYNC},
|
||||
{"aof-rewrite-incremental-fsync",NULL,&g_pserver->aof_rewrite_incremental_fsync,1,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC},
|
||||
{"no-appendfsync-on-rewrite",NULL,&g_pserver->aof_no_fsync_on_rewrite,1,CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE},
|
||||
{"cluster-require-full-coverage",NULL,&g_pserver->cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE},
|
||||
{"rdb-save-incremental-fsync",NULL,&g_pserver->rdb_save_incremental_fsync,1,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC},
|
||||
{"aof-load-truncated",NULL,&g_pserver->aof_load_truncated,1,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED},
|
||||
{"aof-use-rdb-preamble",NULL,&g_pserver->aof_use_rdb_preamble,1,CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE},
|
||||
{"cluster-replica-no-failover","cluster-slave-no-failover",&g_pserver->cluster_slave_no_failover,1,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER},
|
||||
{"replica-lazy-flush","slave-lazy-flush",&g_pserver->repl_slave_lazy_flush,1,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH},
|
||||
{"replica-serve-stale-data","slave-serve-stale-data",&g_pserver->repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA},
|
||||
{"replica-read-only","slave-read-only",&g_pserver->repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY},
|
||||
{"replica-ignore-maxmemory","slave-ignore-maxmemory",&g_pserver->repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY},
|
||||
{"multi-master",NULL,&g_pserver->enable_multimaster,false,CONFIG_DEFAULT_ENABLE_MULTIMASTER},
|
||||
{NULL, NULL, 0, 0}
|
||||
};
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Enum access functions
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -203,6 +244,26 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
sdstolower(argv[0]);
|
||||
|
||||
/* Iterate the configs that are standard */
|
||||
int match = 0;
|
||||
for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
|
||||
if ((!strcasecmp(argv[0],config->name) ||
|
||||
(config->alias && !strcasecmp(argv[0],config->alias))) &&
|
||||
(argc == 2))
|
||||
{
|
||||
if ((*(config->config) = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Execute config directives */
|
||||
if (!strcasecmp(argv[0],"timeout") && argc == 2) {
|
||||
cserver.maxidletime = atoi(argv[1]);
|
||||
@@ -214,10 +275,6 @@ void loadServerConfigFromString(char *config) {
|
||||
if (cserver.tcpkeepalive < 0) {
|
||||
err = "Invalid tcp-keepalive value"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"protected-mode") && argc == 2) {
|
||||
if ((g_pserver->protected_mode = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"port") && argc == 2) {
|
||||
g_pserver->port = atoi(argv[1]);
|
||||
if (g_pserver->port < 0 || g_pserver->port > 65535) {
|
||||
@@ -288,10 +345,6 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"aclfile") && argc == 2) {
|
||||
zfree(g_pserver->acl_filename);
|
||||
g_pserver->acl_filename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"always-show-logo") && argc == 2) {
|
||||
if ((g_pserver->always_show_logo = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
|
||||
if ((g_pserver->syslog_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -368,14 +421,6 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "repl-timeout must be 1 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-disable-tcp-nodelay") && argc==2) {
|
||||
if ((g_pserver->repl_disable_tcp_nodelay = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-diskless-sync") && argc==2) {
|
||||
if ((g_pserver->repl_diskless_sync = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
|
||||
g_pserver->repl_diskless_sync_delay = atoi(argv[1]);
|
||||
if (g_pserver->repl_diskless_sync_delay < 0) {
|
||||
@@ -403,57 +448,6 @@ void loadServerConfigFromString(char *config) {
|
||||
cserver.default_masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL;
|
||||
// Loop through all existing master infos and update them (in case this came after the replicaof config)
|
||||
updateMasterAuth();
|
||||
} else if ((!strcasecmp(argv[0],"slave-serve-stale-data") ||
|
||||
!strcasecmp(argv[0],"replica-serve-stale-data"))
|
||||
&& argc == 2)
|
||||
{
|
||||
if ((g_pserver->repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if ((!strcasecmp(argv[0],"slave-read-only") ||
|
||||
!strcasecmp(argv[0],"replica-read-only"))
|
||||
&& argc == 2)
|
||||
{
|
||||
if ((g_pserver->repl_slave_ro = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if ((!strcasecmp(argv[0],"slave-ignore-maxmemory") ||
|
||||
!strcasecmp(argv[0],"replica-ignore-maxmemory"))
|
||||
&& argc == 2)
|
||||
{
|
||||
if ((g_pserver->repl_slave_ignore_maxmemory = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
|
||||
if ((g_pserver->rdb_compression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) {
|
||||
if ((g_pserver->rdb_checksum = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) {
|
||||
if ((g_pserver->activerehashing = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lazyfree-lazy-eviction") && argc == 2) {
|
||||
if ((g_pserver->lazyfree_lazy_eviction = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lazyfree-lazy-expire") && argc == 2) {
|
||||
if ((g_pserver->lazyfree_lazy_expire = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lazyfree-lazy-server-del") && argc == 2){
|
||||
if ((g_pserver->lazyfree_lazy_server_del = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if ((!strcasecmp(argv[0],"slave-lazy-flush") ||
|
||||
!strcasecmp(argv[0],"replica-lazy-flush")) && argc == 2)
|
||||
{
|
||||
if ((g_pserver->repl_slave_lazy_flush = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"activedefrag") && argc == 2) {
|
||||
if ((cserver.active_defrag_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -463,14 +457,6 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "active defrag can't be enabled without proper jemalloc support"; goto loaderr;
|
||||
#endif
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
|
||||
if ((cserver.daemonize = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"dynamic-hz") && argc == 2) {
|
||||
if ((g_pserver->dynamic_hz = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"hz") && argc == 2) {
|
||||
g_pserver->config_hz = atoi(argv[1]);
|
||||
if (g_pserver->config_hz < CONFIG_MIN_HZ) g_pserver->config_hz = CONFIG_MIN_HZ;
|
||||
@@ -489,11 +475,6 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
zfree(g_pserver->aof_filename);
|
||||
g_pserver->aof_filename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"no-appendfsync-on-rewrite")
|
||||
&& argc == 2) {
|
||||
if ((g_pserver->aof_no_fsync_on_rewrite= yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
|
||||
g_pserver->aof_fsync = configEnumGetValue(aof_fsync_enum,argv[1]);
|
||||
if (g_pserver->aof_fsync == INT_MIN) {
|
||||
@@ -512,28 +493,6 @@ void loadServerConfigFromString(char *config) {
|
||||
argc == 2)
|
||||
{
|
||||
g_pserver->aof_rewrite_min_size = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") &&
|
||||
argc == 2)
|
||||
{
|
||||
if ((g_pserver->aof_rewrite_incremental_fsync =
|
||||
yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rdb-save-incremental-fsync") &&
|
||||
argc == 2)
|
||||
{
|
||||
if ((g_pserver->rdb_save_incremental_fsync =
|
||||
yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"aof-load-truncated") && argc == 2) {
|
||||
if ((g_pserver->aof_load_truncated = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"aof-use-rdb-preamble") && argc == 2) {
|
||||
if ((g_pserver->aof_use_rdb_preamble = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
|
||||
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
||||
@@ -670,13 +629,6 @@ void loadServerConfigFromString(char *config) {
|
||||
{
|
||||
err = "Invalid port"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-require-full-coverage") &&
|
||||
argc == 2)
|
||||
{
|
||||
if ((g_pserver->cluster_require_full_coverage = yesnotoi(argv[1])) == -1)
|
||||
{
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) {
|
||||
g_pserver->cluster_node_timeout = strtoll(argv[1],NULL,10);
|
||||
if (g_pserver->cluster_node_timeout <= 0) {
|
||||
@@ -699,15 +651,6 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "cluster replica validity factor must be zero or positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if ((!strcasecmp(argv[0],"cluster-slave-no-failover") ||
|
||||
!strcasecmp(argv[0],"cluster-replica-no-failover")) &&
|
||||
argc == 2)
|
||||
{
|
||||
g_pserver->cluster_slave_no_failover = yesnotoi(argv[1]);
|
||||
if (g_pserver->cluster_slave_no_failover == -1) {
|
||||
err = "argument must be 'yes' or 'no'";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
|
||||
g_pserver->lua_time_limit = strtoll(argv[1],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
|
||||
@@ -748,11 +691,6 @@ void loadServerConfigFromString(char *config) {
|
||||
cserver.client_obuf_limits[type].hard_limit_bytes = hard;
|
||||
cserver.client_obuf_limits[type].soft_limit_bytes = soft;
|
||||
cserver.client_obuf_limits[type].soft_limit_seconds = soft_seconds;
|
||||
} else if (!strcasecmp(argv[0],"stop-writes-on-bgsave-error") &&
|
||||
argc == 2) {
|
||||
if ((g_pserver->stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if ((!strcasecmp(argv[0],"slave-priority") ||
|
||||
!strcasecmp(argv[0],"replica-priority")) && argc == 2)
|
||||
{
|
||||
@@ -857,15 +795,13 @@ void loadServerConfigFromString(char *config) {
|
||||
g_pserver->fActiveReplica = CONFIG_DEFAULT_ACTIVE_REPLICA;
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"multi-master") && argc == 2){
|
||||
if ((g_pserver->enable_multimaster = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0], "version-override") && argc == 2) {
|
||||
KEYDB_SET_VERSION = zstrdup(argv[1]);
|
||||
serverLog(LL_WARNING, "Warning version is overriden to: %s\n", KEYDB_SET_VERSION);
|
||||
} else if (!strcasecmp(argv[0],"testmode") && argc == 2){
|
||||
g_fTestMode = yesnotoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"rdbfuzz-mode")) {
|
||||
// NOP, handled in main
|
||||
} else {
|
||||
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
||||
}
|
||||
@@ -974,6 +910,19 @@ void configSetCommand(client *c) {
|
||||
serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
|
||||
o = c->argv[3];
|
||||
|
||||
/* Iterate the configs that are standard */
|
||||
for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
|
||||
if(config->modifiable && (!strcasecmp(szFromObj(c->argv[2]),config->name) ||
|
||||
(config->alias && !strcasecmp(szFromObj(c->argv[2]),config->alias))))
|
||||
{
|
||||
int yn = yesnotoi(szFromObj(o));
|
||||
if (yn == -1) goto badfmt;
|
||||
*(config->config) = yn;
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (0) { /* this starts the config_set macros else-if chain. */
|
||||
|
||||
/* Special fields that can't be handled with general macros. */
|
||||
@@ -1141,40 +1090,6 @@ void configSetCommand(client *c) {
|
||||
|
||||
/* Boolean fields.
|
||||
* config_set_bool_field(name,var). */
|
||||
} config_set_bool_field(
|
||||
"rdbcompression", g_pserver->rdb_compression) {
|
||||
} config_set_bool_field(
|
||||
"repl-disable-tcp-nodelay",g_pserver->repl_disable_tcp_nodelay) {
|
||||
} config_set_bool_field(
|
||||
"repl-diskless-sync",g_pserver->repl_diskless_sync) {
|
||||
} config_set_bool_field(
|
||||
"cluster-require-full-coverage",g_pserver->cluster_require_full_coverage) {
|
||||
} config_set_bool_field(
|
||||
"cluster-slave-no-failover",g_pserver->cluster_slave_no_failover) {
|
||||
} config_set_bool_field(
|
||||
"cluster-replica-no-failover",g_pserver->cluster_slave_no_failover) {
|
||||
} config_set_bool_field(
|
||||
"aof-rewrite-incremental-fsync",g_pserver->aof_rewrite_incremental_fsync) {
|
||||
} config_set_bool_field(
|
||||
"rdb-save-incremental-fsync",g_pserver->rdb_save_incremental_fsync) {
|
||||
} config_set_bool_field(
|
||||
"aof-load-truncated",g_pserver->aof_load_truncated) {
|
||||
} config_set_bool_field(
|
||||
"aof-use-rdb-preamble",g_pserver->aof_use_rdb_preamble) {
|
||||
} config_set_bool_field(
|
||||
"slave-serve-stale-data",g_pserver->repl_serve_stale_data) {
|
||||
} config_set_bool_field(
|
||||
"replica-serve-stale-data",g_pserver->repl_serve_stale_data) {
|
||||
} config_set_bool_field(
|
||||
"slave-read-only",g_pserver->repl_slave_ro) {
|
||||
} config_set_bool_field(
|
||||
"replica-read-only",g_pserver->repl_slave_ro) {
|
||||
} config_set_bool_field(
|
||||
"slave-ignore-maxmemory",g_pserver->repl_slave_ignore_maxmemory) {
|
||||
} config_set_bool_field(
|
||||
"replica-ignore-maxmemory",g_pserver->repl_slave_ignore_maxmemory) {
|
||||
} config_set_bool_field(
|
||||
"activerehashing",g_pserver->activerehashing) {
|
||||
} config_set_bool_field(
|
||||
"activedefrag",cserver.active_defrag_enabled) {
|
||||
#ifndef HAVE_DEFRAG
|
||||
@@ -1188,24 +1103,6 @@ void configSetCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
} config_set_bool_field(
|
||||
"protected-mode",g_pserver->protected_mode) {
|
||||
} config_set_bool_field(
|
||||
"stop-writes-on-bgsave-error",g_pserver->stop_writes_on_bgsave_err) {
|
||||
} config_set_bool_field(
|
||||
"lazyfree-lazy-eviction",g_pserver->lazyfree_lazy_eviction) {
|
||||
} config_set_bool_field(
|
||||
"lazyfree-lazy-expire",g_pserver->lazyfree_lazy_expire) {
|
||||
} config_set_bool_field(
|
||||
"lazyfree-lazy-server-del",g_pserver->lazyfree_lazy_server_del) {
|
||||
} config_set_bool_field(
|
||||
"slave-lazy-flush",g_pserver->repl_slave_lazy_flush) {
|
||||
} config_set_bool_field(
|
||||
"replica-lazy-flush",g_pserver->repl_slave_lazy_flush) {
|
||||
} config_set_bool_field(
|
||||
"no-appendfsync-on-rewrite",g_pserver->aof_no_fsync_on_rewrite) {
|
||||
} config_set_bool_field(
|
||||
"dynamic-hz",g_pserver->dynamic_hz) {
|
||||
|
||||
/* Numerical fields.
|
||||
* config_set_numerical_field(name,var,min,max) */
|
||||
@@ -1342,8 +1239,6 @@ void configSetCommand(client *c) {
|
||||
"loglevel",cserver.verbosity,loglevel_enum) {
|
||||
} config_set_enum_field(
|
||||
"maxmemory-policy",g_pserver->maxmemory_policy,maxmemory_policy_enum) {
|
||||
} config_set_bool_field(
|
||||
"multi-master", g_pserver->enable_multimaster) {
|
||||
} config_set_enum_field(
|
||||
"appendfsync",g_pserver->aof_fsync,aof_fsync_enum) {
|
||||
|
||||
@@ -1496,58 +1391,15 @@ void configGetCommand(client *c) {
|
||||
config_get_numerical_field("tcp-keepalive",cserver.tcpkeepalive);
|
||||
|
||||
/* Bool (yes/no) values */
|
||||
config_get_bool_field("cluster-require-full-coverage",
|
||||
g_pserver->cluster_require_full_coverage);
|
||||
config_get_bool_field("cluster-slave-no-failover",
|
||||
g_pserver->cluster_slave_no_failover);
|
||||
config_get_bool_field("cluster-replica-no-failover",
|
||||
g_pserver->cluster_slave_no_failover);
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
g_pserver->aof_no_fsync_on_rewrite);
|
||||
config_get_bool_field("slave-serve-stale-data",
|
||||
g_pserver->repl_serve_stale_data);
|
||||
config_get_bool_field("replica-serve-stale-data",
|
||||
g_pserver->repl_serve_stale_data);
|
||||
config_get_bool_field("slave-read-only",
|
||||
g_pserver->repl_slave_ro);
|
||||
config_get_bool_field("replica-read-only",
|
||||
g_pserver->repl_slave_ro);
|
||||
config_get_bool_field("slave-ignore-maxmemory",
|
||||
g_pserver->repl_slave_ignore_maxmemory);
|
||||
config_get_bool_field("replica-ignore-maxmemory",
|
||||
g_pserver->repl_slave_ignore_maxmemory);
|
||||
config_get_bool_field("stop-writes-on-bgsave-error",
|
||||
g_pserver->stop_writes_on_bgsave_err);
|
||||
config_get_bool_field("daemonize", cserver.daemonize);
|
||||
config_get_bool_field("rdbcompression", g_pserver->rdb_compression);
|
||||
config_get_bool_field("rdbchecksum", g_pserver->rdb_checksum);
|
||||
config_get_bool_field("activerehashing", g_pserver->activerehashing);
|
||||
/* Iterate the configs that are standard */
|
||||
for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
|
||||
config_get_bool_field(config->name, *(config->config));
|
||||
if (config->alias) {
|
||||
config_get_bool_field(config->alias, *(config->config));
|
||||
}
|
||||
}
|
||||
|
||||
config_get_bool_field("activedefrag", cserver.active_defrag_enabled);
|
||||
config_get_bool_field("protected-mode", g_pserver->protected_mode);
|
||||
config_get_bool_field("repl-disable-tcp-nodelay",
|
||||
g_pserver->repl_disable_tcp_nodelay);
|
||||
config_get_bool_field("repl-diskless-sync",
|
||||
g_pserver->repl_diskless_sync);
|
||||
config_get_bool_field("aof-rewrite-incremental-fsync",
|
||||
g_pserver->aof_rewrite_incremental_fsync);
|
||||
config_get_bool_field("rdb-save-incremental-fsync",
|
||||
g_pserver->rdb_save_incremental_fsync);
|
||||
config_get_bool_field("aof-load-truncated",
|
||||
g_pserver->aof_load_truncated);
|
||||
config_get_bool_field("aof-use-rdb-preamble",
|
||||
g_pserver->aof_use_rdb_preamble);
|
||||
config_get_bool_field("lazyfree-lazy-eviction",
|
||||
g_pserver->lazyfree_lazy_eviction);
|
||||
config_get_bool_field("lazyfree-lazy-expire",
|
||||
g_pserver->lazyfree_lazy_expire);
|
||||
config_get_bool_field("lazyfree-lazy-server-del",
|
||||
g_pserver->lazyfree_lazy_server_del);
|
||||
config_get_bool_field("slave-lazy-flush",
|
||||
g_pserver->repl_slave_lazy_flush);
|
||||
config_get_bool_field("replica-lazy-flush",
|
||||
g_pserver->repl_slave_lazy_flush);
|
||||
config_get_bool_field("dynamic-hz",
|
||||
g_pserver->dynamic_hz);
|
||||
|
||||
/* Enum values */
|
||||
config_get_enum_field("maxmemory-policy",
|
||||
@@ -2282,7 +2134,11 @@ int rewriteConfig(char *path) {
|
||||
/* Step 2: rewrite every single option, replacing or appending it inside
|
||||
* the rewrite state. */
|
||||
|
||||
rewriteConfigYesNoOption(state,"daemonize",cserver.daemonize,0);
|
||||
/* Iterate the configs that are standard */
|
||||
for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
|
||||
rewriteConfigYesNoOption(state,config->name,*(config->config),config->default_value);
|
||||
}
|
||||
|
||||
rewriteConfigStringOption(state,"pidfile",cserver.pidfile,CONFIG_DEFAULT_PID_FILE);
|
||||
rewriteConfigNumericalOption(state,"port",g_pserver->port,CONFIG_DEFAULT_SERVER_PORT);
|
||||
rewriteConfigNumericalOption(state,"cluster-announce-port",g_pserver->cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT);
|
||||
@@ -2303,9 +2159,6 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigSaveOption(state);
|
||||
rewriteConfigUserOption(state);
|
||||
rewriteConfigNumericalOption(state,"databases",cserver.dbnum,CONFIG_DEFAULT_DBNUM);
|
||||
rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",g_pserver->stop_writes_on_bgsave_err,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
|
||||
rewriteConfigYesNoOption(state,"rdbcompression",g_pserver->rdb_compression,CONFIG_DEFAULT_RDB_COMPRESSION);
|
||||
rewriteConfigYesNoOption(state,"rdbchecksum",g_pserver->rdb_checksum,CONFIG_DEFAULT_RDB_CHECKSUM);
|
||||
rewriteConfigStringOption(state,"dbfilename",g_pserver->rdb_filename,CONFIG_DEFAULT_RDB_FILENAME);
|
||||
rewriteConfigDirOption(state);
|
||||
rewriteConfigSlaveofOption(state,"replicaof");
|
||||
@@ -2313,15 +2166,10 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigStringOption(state,"masteruser",cserver.default_masteruser,NULL);
|
||||
rewriteConfigStringOption(state,"masterauth",cserver.default_masterauth,NULL);
|
||||
rewriteConfigStringOption(state,"cluster-announce-ip",g_pserver->cluster_announce_ip,NULL);
|
||||
rewriteConfigYesNoOption(state,"replica-serve-stale-data",g_pserver->repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA);
|
||||
rewriteConfigYesNoOption(state,"replica-read-only",g_pserver->repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY);
|
||||
rewriteConfigYesNoOption(state,"replica-ignore-maxmemory",g_pserver->repl_slave_ignore_maxmemory,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY);
|
||||
rewriteConfigNumericalOption(state,"repl-ping-replica-period",g_pserver->repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
|
||||
rewriteConfigNumericalOption(state,"repl-timeout",g_pserver->repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT);
|
||||
rewriteConfigBytesOption(state,"repl-backlog-size",g_pserver->repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE);
|
||||
rewriteConfigBytesOption(state,"repl-backlog-ttl",g_pserver->repl_backlog_time_limit,CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
|
||||
rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",g_pserver->repl_disable_tcp_nodelay,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY);
|
||||
rewriteConfigYesNoOption(state,"repl-diskless-sync",g_pserver->repl_diskless_sync,CONFIG_DEFAULT_REPL_DISKLESS_SYNC);
|
||||
rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",g_pserver->repl_diskless_sync_delay,CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY);
|
||||
rewriteConfigNumericalOption(state,"replica-priority",g_pserver->slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
|
||||
rewriteConfigNumericalOption(state,"min-replicas-to-write",g_pserver->repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
|
||||
@@ -2344,14 +2192,11 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"appendonly",g_pserver->aof_state != AOF_OFF,0);
|
||||
rewriteConfigStringOption(state,"appendfilename",g_pserver->aof_filename,CONFIG_DEFAULT_AOF_FILENAME);
|
||||
rewriteConfigEnumOption(state,"appendfsync",g_pserver->aof_fsync,aof_fsync_enum,CONFIG_DEFAULT_AOF_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",g_pserver->aof_no_fsync_on_rewrite,CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE);
|
||||
rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",g_pserver->aof_rewrite_perc,AOF_REWRITE_PERC);
|
||||
rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",g_pserver->aof_rewrite_min_size,AOF_REWRITE_MIN_SIZE);
|
||||
rewriteConfigNumericalOption(state,"lua-time-limit",g_pserver->lua_time_limit,LUA_SCRIPT_TIME_LIMIT);
|
||||
rewriteConfigYesNoOption(state,"cluster-enabled",g_pserver->cluster_enabled,0);
|
||||
rewriteConfigStringOption(state,"cluster-config-file",g_pserver->cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigYesNoOption(state,"cluster-require-full-coverage",g_pserver->cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
|
||||
rewriteConfigYesNoOption(state,"cluster-replica-no-failover",g_pserver->cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER);
|
||||
rewriteConfigNumericalOption(state,"cluster-node-timeout",g_pserver->cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT);
|
||||
rewriteConfigNumericalOption(state,"cluster-migration-barrier",g_pserver->cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER);
|
||||
rewriteConfigNumericalOption(state,"cluster-replica-validity-factor",g_pserver->cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
|
||||
@@ -2369,23 +2214,11 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",g_pserver->zset_max_ziplist_entries,OBJ_ZSET_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",g_pserver->zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",g_pserver->hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES);
|
||||
rewriteConfigYesNoOption(state,"activerehashing",g_pserver->activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
|
||||
rewriteConfigYesNoOption(state,"activedefrag",cserver.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG);
|
||||
rewriteConfigYesNoOption(state,"protected-mode",g_pserver->protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",g_pserver->config_hz,CONFIG_DEFAULT_HZ);
|
||||
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",g_pserver->aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"rdb-save-incremental-fsync",g_pserver->rdb_save_incremental_fsync,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"aof-load-truncated",g_pserver->aof_load_truncated,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED);
|
||||
rewriteConfigYesNoOption(state,"aof-use-rdb-preamble",g_pserver->aof_use_rdb_preamble,CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE);
|
||||
rewriteConfigEnumOption(state,"supervised",cserver.supervised_mode,supervised_mode_enum,SUPERVISED_NONE);
|
||||
rewriteConfigYesNoOption(state,"lazyfree-lazy-eviction",g_pserver->lazyfree_lazy_eviction,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION);
|
||||
rewriteConfigYesNoOption(state,"lazyfree-lazy-expire",g_pserver->lazyfree_lazy_expire,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE);
|
||||
rewriteConfigYesNoOption(state,"lazyfree-lazy-server-del",g_pserver->lazyfree_lazy_server_del,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL);
|
||||
rewriteConfigYesNoOption(state,"replica-lazy-flush",g_pserver->repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH);
|
||||
rewriteConfigYesNoOption(state,"dynamic-hz",g_pserver->dynamic_hz,CONFIG_DEFAULT_DYNAMIC_HZ);
|
||||
rewriteConfigYesNoOption(state,"active-replica",g_pserver->fActiveReplica,CONFIG_DEFAULT_ACTIVE_REPLICA);
|
||||
rewriteConfigYesNoOption(state,"multi-master",g_pserver->enable_multimaster,CONFIG_DEFAULT_ENABLE_MULTIMASTER);
|
||||
rewriteConfigStringOption(state, "version-override",KEYDB_SET_VERSION,KEYDB_REAL_VERSION);
|
||||
|
||||
/* Rewrite Sentinel config if in Sentinel mode. */
|
||||
|
||||
+625
-129
File diff suppressed because it is too large.
Load diff
+40
-42
@@ -55,6 +55,8 @@ typedef ucontext_t sigcontext_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool g_fInCrash = false;
|
||||
|
||||
/* ================================= Debugging ============================== */
|
||||
|
||||
/* Compute the sha1 of string at 's' with 'len' bytes long.
|
||||
@@ -124,9 +126,13 @@ void mixStringObjectDigest(unsigned char *digest, robj_roptr o) {
|
||||
void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj_roptr o) {
|
||||
uint32_t aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
long long expiretime = getExpire(db,keyobj);
|
||||
expireEntry *pexpire = db->getExpire(keyobj);
|
||||
long long expiretime = -1;
|
||||
char buf[128];
|
||||
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expiretime);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
mixStringObjectDigest(digest,o);
|
||||
@@ -260,8 +266,6 @@ void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj
|
||||
* a different digest. */
|
||||
void computeDatasetDigest(unsigned char *final) {
|
||||
unsigned char digest[20];
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
uint32_t aux;
|
||||
|
||||
@@ -270,8 +274,7 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
redisDb *db = g_pserver->db+j;
|
||||
|
||||
if (dictSize(db->pdict) == 0) continue;
|
||||
di = dictGetSafeIterator(db->pdict);
|
||||
if (db->size() == 0) continue;
|
||||
|
||||
/* hash the DB id, so the same dataset moved in a different
|
||||
* DB will lead to a different digest */
|
||||
@@ -279,24 +282,21 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
mixDigest(final,&aux,sizeof(aux));
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key;
|
||||
robj *keyobj, *o;
|
||||
db->iterate([&](const char *key, robj *o)->bool {
|
||||
robj *keyobj;
|
||||
|
||||
memset(digest,0,20); /* This key-val digest */
|
||||
key = (sds)dictGetKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
mixDigest(digest,key,sdslen(key));
|
||||
|
||||
o = (robj*)dictGetVal(de);
|
||||
xorObjectDigest(db,keyobj,digest,o);
|
||||
|
||||
/* We can finally xor the key-val digest to the final digest */
|
||||
xorDigest(final,digest,20);
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ NULL
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(rsiptr) != C_OK) {
|
||||
if (rdbSave(nullptr, rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
@@ -388,15 +388,14 @@ NULL
|
||||
serverLog(LL_WARNING,"Append Only File loaded by DEBUG LOADAOF");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"object") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
robj *val;
|
||||
const char *strenc;
|
||||
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(c->argv[2]))) == NULL) {
|
||||
val = c->db->find(c->argv[2]);
|
||||
if (val == NULL) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = (robj*)dictGetVal(de);
|
||||
strenc = strEncoding(val->encoding);
|
||||
|
||||
char extra[138] = {0};
|
||||
@@ -436,20 +435,18 @@ NULL
|
||||
"Value at:%p refcount:%d "
|
||||
"encoding:%s serializedlength:%zu "
|
||||
"lru:%d lru_seconds_idle:%llu%s",
|
||||
(void*)val, static_cast<int>(val->refcount),
|
||||
(void*)val, static_cast<int>(val->getrefcount(std::memory_order_relaxed)),
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"sdslen") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
robj *val;
|
||||
sds key;
|
||||
auto itr = c->db->find(c->argv[2]);
|
||||
robj *val = itr.val();
|
||||
const char *key = itr.key();
|
||||
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(c->argv[2]))) == NULL) {
|
||||
if (val == NULL) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = (robj*)dictGetVal(de);
|
||||
key = (sds)dictGetKey(de);
|
||||
|
||||
if (val->type != OBJ_STRING || !sdsEncodedObject(val)) {
|
||||
addReplyError(c,"Not an sds encoded string.");
|
||||
@@ -459,16 +456,16 @@ NULL
|
||||
"val_sds_len:%lld, val_sds_avail:%lld, val_zmalloc: %lld",
|
||||
(long long) sdslen(key),
|
||||
(long long) sdsavail(key),
|
||||
(long long) sdsZmallocSize(key),
|
||||
(long long) sdsZmallocSize((sds)key),
|
||||
(long long) sdslen(szFromObj(val)),
|
||||
(long long) sdsavail(szFromObj(val)),
|
||||
(long long) getStringObjectSdsUsedMemory(val));
|
||||
}
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"ziplist") && c->argc == 3) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr))
|
||||
== NULL) return;
|
||||
== nullptr) return;
|
||||
|
||||
if (o->encoding != OBJ_ENCODING_ZIPLIST) {
|
||||
addReplyError(c,"Not an sds encoded string.");
|
||||
@@ -484,7 +481,7 @@ NULL
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != C_OK)
|
||||
return;
|
||||
dictExpand(c->db->pdict,keys);
|
||||
c->db->expand(keys);
|
||||
for (j = 0; j < keys; j++) {
|
||||
long valsize = 0;
|
||||
snprintf(buf,sizeof(buf),"%s:%lu",
|
||||
@@ -635,20 +632,20 @@ NULL
|
||||
}
|
||||
|
||||
stats = sdscatprintf(stats,"[Dictionary HT]\n");
|
||||
dictGetStats(buf,sizeof(buf),g_pserver->db[dbid].pdict);
|
||||
g_pserver->db[dbid].getStats(buf,sizeof(buf));
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
stats = sdscatprintf(stats,"[Expires HT]\n");
|
||||
dictGetStats(buf,sizeof(buf),g_pserver->db[dbid].expires);
|
||||
stats = sdscat(stats,buf);
|
||||
stats = sdscatprintf(stats,"[Expires set]\n");
|
||||
g_pserver->db[dbid].getExpireStats(buf, sizeof(buf));
|
||||
stats = sdscat(stats, buf);
|
||||
|
||||
addReplyBulkSds(c,stats);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"htstats-key") && c->argc == 3) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
dict *ht = NULL;
|
||||
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr))
|
||||
== NULL) return;
|
||||
== nullptr) return;
|
||||
|
||||
/* Get the hash table reference from the object, if possible. */
|
||||
switch (o->encoding) {
|
||||
@@ -676,10 +673,12 @@ NULL
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2)
|
||||
{
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2) {
|
||||
stringmatchlen_fuzz_test();
|
||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]), "force-master") && c->argc == 2) {
|
||||
c->flags |= CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY;
|
||||
addReply(c, shared.ok);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -706,7 +705,7 @@ void _serverAssertPrintClientInfo(const client *c) {
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
|
||||
serverLog(LL_WARNING,"client->flags = %d", static_cast<int>(c->flags));
|
||||
serverLog(LL_WARNING,"client->flags = %llu", static_cast<unsigned long long>(c->flags));
|
||||
serverLog(LL_WARNING,"client->fd = %d", c->fd);
|
||||
serverLog(LL_WARNING,"client->argc = %d", c->argc);
|
||||
for (j=0; j < c->argc; j++) {
|
||||
@@ -721,14 +720,14 @@ void _serverAssertPrintClientInfo(const client *c) {
|
||||
arg = buf;
|
||||
}
|
||||
serverLog(LL_WARNING,"client->argv[%d] = \"%s\" (refcount: %d)",
|
||||
j, arg, static_cast<int>(c->argv[j]->refcount));
|
||||
j, arg, static_cast<int>(c->argv[j]->getrefcount(std::memory_order_relaxed)));
|
||||
}
|
||||
}
|
||||
|
||||
void serverLogObjectDebugInfo(robj_roptr o) {
|
||||
serverLog(LL_WARNING,"Object type: %d", o->type);
|
||||
serverLog(LL_WARNING,"Object encoding: %d", o->encoding);
|
||||
serverLog(LL_WARNING,"Object refcount: %d", static_cast<int>(o->refcount));
|
||||
serverLog(LL_WARNING,"Object refcount: %d", static_cast<int>(o->getrefcount(std::memory_order_relaxed)));
|
||||
if (o->type == OBJ_STRING && sdsEncodedObject(o)) {
|
||||
serverLog(LL_WARNING,"Object raw string len: %zu", sdslen(szFromObj(o)));
|
||||
if (sdslen(szFromObj(o)) < 4096) {
|
||||
@@ -1248,12 +1247,10 @@ void logCurrentClient(void) {
|
||||
* selected DB, and if so print info about the associated object. */
|
||||
if (cc->argc >= 1) {
|
||||
robj *val, *key;
|
||||
dictEntry *de;
|
||||
|
||||
key = getDecodedObject(cc->argv[1]);
|
||||
de = dictFind(cc->db->pdict, ptrFromObj(key));
|
||||
if (de) {
|
||||
val = (robj*)dictGetVal(de);
|
||||
val = cc->db->find(key);
|
||||
if (val) {
|
||||
serverLog(LL_WARNING,"key '%s' found in DB containing the following object:", (char*)ptrFromObj(key));
|
||||
serverLogObjectDebugInfo(val);
|
||||
}
|
||||
@@ -1356,6 +1353,7 @@ void dumpX86Calls(void *addr, size_t len) {
|
||||
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
ucontext_t *uc = (ucontext_t*) secret;
|
||||
g_fInCrash = true;
|
||||
void *eip = getMcontextEip(uc);
|
||||
sds infostring, clients;
|
||||
struct sigaction act;
|
||||
|
||||
+25
-15
@@ -48,6 +48,7 @@ extern "C" int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
|
||||
/* forward declarations*/
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
|
||||
void replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
@@ -102,7 +103,7 @@ sds activeDefragSds(sds sdsptr) {
|
||||
* and should NOT be accessed. */
|
||||
robj *activeDefragStringOb(robj* ob, long *defragged) {
|
||||
robj *ret = NULL;
|
||||
if (ob->refcount!=1)
|
||||
if (ob->getrefcount(std::memory_order_relaxed)!=1)
|
||||
return NULL;
|
||||
|
||||
/* try to defrag robj (only if not an EMBSTR type (handled below). */
|
||||
@@ -406,6 +407,17 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
|
||||
auto itr = set.find(oldkey);
|
||||
if (itr != set.end())
|
||||
{
|
||||
expireEntry eNew(std::move(*itr));
|
||||
eNew.setKeyUnsafe(newkey);
|
||||
set.erase(itr);
|
||||
set.insert(eNew);
|
||||
}
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head, *newnode;
|
||||
long defragged = 0;
|
||||
@@ -769,16 +781,14 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
newsds = activeDefragSds(keysds);
|
||||
if (newsds)
|
||||
defragged++, de->key = newsds;
|
||||
if (dictSize(db->expires)) {
|
||||
/* Dirty code:
|
||||
* I can't search in db->expires for that key after i already released
|
||||
* the pointer it holds it won't be able to do the string compare */
|
||||
uint64_t hash = dictGetHash(db->pdict, de->key);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
|
||||
if (!db->setexpire()->empty()) {
|
||||
replaceSateliteOSetKeyPtr(*const_cast<expireset*>(db->setexpire()), keysds, newsds);
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
ob = (robj*)dictGetVal(de);
|
||||
if (ob == nullptr)
|
||||
return defragged;
|
||||
if ((newob = activeDefragStringOb(ob, &defragged))) {
|
||||
de->v.val = newob;
|
||||
ob = newob;
|
||||
@@ -845,7 +855,7 @@ void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
g_pserver->stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for each hash table bicket,
|
||||
/* Defrag scan callback for each hash table bucket,
|
||||
* used in order to defrag the dictEntry allocations. */
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
UNUSED(privdata); /* NOTE: this function is also used by both activeDefragCycle and scanLaterHash, etc. don't use privdata */
|
||||
@@ -894,9 +904,8 @@ long defragOtherGlobals() {
|
||||
|
||||
/* returns 0 more work may or may not be needed (see non-zero cursor),
|
||||
* and 1 if time is up and more work is needed. */
|
||||
int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = (robj*)dictGetVal(de);
|
||||
int defragLaterItem(robj *ob, unsigned long *cursor, long long endtime) {
|
||||
if (ob) {
|
||||
if (ob->type == OBJ_LIST) {
|
||||
g_pserver->stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
@@ -951,11 +960,11 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
}
|
||||
|
||||
/* each time we enter this function we need to fetch the key from the dict again (if it still exists) */
|
||||
dictEntry *de = dictFind(db->pdict, current_key);
|
||||
robj *o = db->find(current_key);
|
||||
key_defragged = g_pserver->stat_active_defrag_hits;
|
||||
do {
|
||||
int quit = 0;
|
||||
if (defragLaterItem(de, &cursor, endtime))
|
||||
if (defragLaterItem(o, &cursor, endtime))
|
||||
quit = 1; /* time is up, we didn't finish all the work */
|
||||
|
||||
/* Don't start a new BIG key in this loop, this is because the
|
||||
@@ -1037,7 +1046,7 @@ void activeDefragCycle(void) {
|
||||
mstime_t latency;
|
||||
int quit = 0;
|
||||
|
||||
if (g_pserver->aof_child_pid!=-1 || g_pserver->rdb_child_pid!=-1)
|
||||
if (g_pserver->aof_child_pid!=-1 || g_pserver->FRdbSaveInProgress())
|
||||
return; /* Defragging memory while there's a fork will just do damage. */
|
||||
|
||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||
@@ -1104,7 +1113,8 @@ void activeDefragCycle(void) {
|
||||
break; /* this will exit the function and we'll continue on the next cycle */
|
||||
}
|
||||
|
||||
cursor = dictScan(db->pdict, cursor, defragScanCallback, defragDictBucketCallback, db);
|
||||
// we actually look at the objects too but defragScanCallback can handle missing values
|
||||
cursor = dictScan(db->dictUnsafeKeyOnly(), cursor, defragScanCallback, defragDictBucketCallback, db);
|
||||
|
||||
/* Once in 16 scan iterations, 512 pointer reallocations. or 64 keys
|
||||
* (if we have a lot of pointers in one hash bucket or rehasing),
|
||||
|
||||
+162
-133
@@ -87,7 +87,7 @@ unsigned int LRU_CLOCK(void) {
|
||||
|
||||
/* Given an object returns the min number of milliseconds the object was never
|
||||
* requested, using an approximated LRU algorithm. */
|
||||
unsigned long long estimateObjectIdleTime(robj *o) {
|
||||
unsigned long long estimateObjectIdleTime(robj_roptr o) {
|
||||
unsigned long long lruclock = LRU_CLOCK();
|
||||
if (lruclock >= o->lru) {
|
||||
return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
|
||||
@@ -150,6 +150,84 @@ void evictionPoolAlloc(void) {
|
||||
EvictionPoolLRU = ep;
|
||||
}
|
||||
|
||||
void processEvictionCandidate(int dbid, sds key, robj *o, const expireEntry *e, struct evictionPoolEntry *pool)
|
||||
{
|
||||
unsigned long long idle;
|
||||
|
||||
/* Calculate the idle time according to the policy. This is called
|
||||
* idle just because the code initially handled LRU, but is in fact
|
||||
* just a score where an higher score means better candidate. */
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
idle = (o != nullptr) ? estimateObjectIdleTime(o) : 0;
|
||||
} else if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
/* When we use an LRU policy, we sort the keys by idle time
|
||||
* so that we expire keys starting from greater idle time.
|
||||
* However when the policy is an LFU one, we have a frequency
|
||||
* estimation, and we want to evict keys with lower frequency
|
||||
* first. So inside the pool we put objects using the inverted
|
||||
* frequency subtracting the actual frequency to the maximum
|
||||
* frequency of 255. */
|
||||
idle = 255-LFUDecrAndReturn(o);
|
||||
} else if (g_pserver->maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
|
||||
/* In this case the sooner the expire the better. */
|
||||
idle = ULLONG_MAX - e->when();
|
||||
} else {
|
||||
serverPanic("Unknown eviction policy in evictionPoolPopulate()");
|
||||
}
|
||||
|
||||
/* Insert the element inside the pool.
|
||||
* First, find the first empty bucket or the first populated
|
||||
* bucket that has an idle time smaller than our idle time. */
|
||||
int k = 0;
|
||||
while (k < EVPOOL_SIZE &&
|
||||
pool[k].key &&
|
||||
pool[k].idle < idle) k++;
|
||||
if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {
|
||||
/* Can't insert if the element is < the worst element we have
|
||||
* and there are no empty buckets. */
|
||||
return;
|
||||
} else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
|
||||
/* Inserting into empty position. No setup needed before insert. */
|
||||
} else {
|
||||
/* Inserting in the middle. Now k points to the first element
|
||||
* greater than the element to insert. */
|
||||
if (pool[EVPOOL_SIZE-1].key == NULL) {
|
||||
/* Free space on the right? Insert at k shifting
|
||||
* all the elements from k to end to the right. */
|
||||
|
||||
/* Save SDS before overwriting. */
|
||||
sds cached = pool[EVPOOL_SIZE-1].cached;
|
||||
memmove(pool+k+1,pool+k,
|
||||
sizeof(pool[0])*(EVPOOL_SIZE-k-1));
|
||||
pool[k].cached = cached;
|
||||
} else {
|
||||
/* No free space on right? Insert at k-1 */
|
||||
k--;
|
||||
/* Shift all elements on the left of k (included) to the
|
||||
* left, so we discard the element with smaller idle time. */
|
||||
sds cached = pool[0].cached; /* Save SDS before overwriting. */
|
||||
if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
|
||||
memmove(pool,pool+1,sizeof(pool[0])*k);
|
||||
pool[k].cached = cached;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to reuse the cached SDS string allocated in the pool entry,
|
||||
* because allocating and deallocating this object is costly
|
||||
* (according to the profiler, not my fantasy. Remember:
|
||||
* premature optimizbla bla bla bla. */
|
||||
int klen = sdslen(key);
|
||||
if (klen > EVPOOL_CACHED_SDS_SIZE) {
|
||||
pool[k].key = sdsdup(key);
|
||||
} else {
|
||||
memcpy(pool[k].cached,key,klen+1);
|
||||
sdssetlen(pool[k].cached,klen);
|
||||
pool[k].key = pool[k].cached;
|
||||
}
|
||||
pool[k].idle = idle;
|
||||
pool[k].dbid = dbid;
|
||||
}
|
||||
|
||||
/* This is an helper function for freeMemoryIfNeeded(), it is used in order
|
||||
* to populate the evictionPool with a few entries every time we want to
|
||||
* expire a key. Keys with idle time smaller than one of the current
|
||||
@@ -159,100 +237,37 @@ void evictionPoolAlloc(void) {
|
||||
* idle time are on the left, and keys with the higher idle time on the
|
||||
* right. */
|
||||
|
||||
void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
|
||||
int j, k, count;
|
||||
dictEntry **samples = (dictEntry**)alloca(g_pserver->maxmemory_samples * sizeof(dictEntry*));
|
||||
struct visitFunctor
|
||||
{
|
||||
int dbid;
|
||||
dict *dbdict;
|
||||
struct evictionPoolEntry *pool;
|
||||
int count;
|
||||
|
||||
count = dictGetSomeKeys(sampledict,samples,g_pserver->maxmemory_samples);
|
||||
for (j = 0; j < count; j++) {
|
||||
unsigned long long idle;
|
||||
sds key;
|
||||
robj *o = nullptr;
|
||||
dictEntry *de;
|
||||
|
||||
de = samples[j];
|
||||
key = (sds)dictGetKey(de);
|
||||
|
||||
/* If the dictionary we are sampling from is not the main
|
||||
* dictionary (but the expires one) we need to lookup the key
|
||||
* again in the key dictionary to obtain the value object. */
|
||||
if (g_pserver->maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
|
||||
if (sampledict != keydict) de = dictFind(keydict, key);
|
||||
o = (robj*)dictGetVal(de);
|
||||
bool operator()(const expireEntry &e)
|
||||
{
|
||||
dictEntry *de = dictFind(dbdict, e.key());
|
||||
processEvictionCandidate(dbid, (sds)dictGetKey(de), (robj*)dictGetVal(de), &e, pool);
|
||||
++count;
|
||||
return count < g_pserver->maxmemory_samples;
|
||||
}
|
||||
};
|
||||
void evictionPoolPopulate(int dbid, redisDb *db, expireset *setexpire, struct evictionPoolEntry *pool)
|
||||
{
|
||||
if (setexpire != nullptr)
|
||||
{
|
||||
visitFunctor visitor { dbid, db->dictUnsafeKeyOnly(), pool, 0 };
|
||||
setexpire->random_visit(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
dictEntry **samples = (dictEntry**)alloca(g_pserver->maxmemory_samples * sizeof(dictEntry*));
|
||||
int count = dictGetSomeKeys(db->dictUnsafeKeyOnly(),samples,g_pserver->maxmemory_samples);
|
||||
for (int j = 0; j < count; j++) {
|
||||
robj *o = (robj*)dictGetVal(samples[j]);
|
||||
serverAssert(o != nullptr); // BUG!!! We have to get the info we need here without permanently rehydrating the obj
|
||||
processEvictionCandidate(dbid, (sds)dictGetKey(samples[j]), o, nullptr, pool);
|
||||
}
|
||||
|
||||
/* Calculate the idle time according to the policy. This is called
|
||||
* idle just because the code initially handled LRU, but is in fact
|
||||
* just a score where an higher score means better candidate. */
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
idle = (o != nullptr) ? estimateObjectIdleTime(o) : 0;
|
||||
} else if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
/* When we use an LRU policy, we sort the keys by idle time
|
||||
* so that we expire keys starting from greater idle time.
|
||||
* However when the policy is an LFU one, we have a frequency
|
||||
* estimation, and we want to evict keys with lower frequency
|
||||
* first. So inside the pool we put objects using the inverted
|
||||
* frequency subtracting the actual frequency to the maximum
|
||||
* frequency of 255. */
|
||||
idle = 255-LFUDecrAndReturn(o);
|
||||
} else if (g_pserver->maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
|
||||
/* In this case the sooner the expire the better. */
|
||||
idle = ULLONG_MAX - (long)dictGetVal(de);
|
||||
} else {
|
||||
serverPanic("Unknown eviction policy in evictionPoolPopulate()");
|
||||
}
|
||||
|
||||
/* Insert the element inside the pool.
|
||||
* First, find the first empty bucket or the first populated
|
||||
* bucket that has an idle time smaller than our idle time. */
|
||||
k = 0;
|
||||
while (k < EVPOOL_SIZE &&
|
||||
pool[k].key &&
|
||||
pool[k].idle < idle) k++;
|
||||
if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {
|
||||
/* Can't insert if the element is < the worst element we have
|
||||
* and there are no empty buckets. */
|
||||
continue;
|
||||
} else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
|
||||
/* Inserting into empty position. No setup needed before insert. */
|
||||
} else {
|
||||
/* Inserting in the middle. Now k points to the first element
|
||||
* greater than the element to insert. */
|
||||
if (pool[EVPOOL_SIZE-1].key == NULL) {
|
||||
/* Free space on the right? Insert at k shifting
|
||||
* all the elements from k to end to the right. */
|
||||
|
||||
/* Save SDS before overwriting. */
|
||||
sds cached = pool[EVPOOL_SIZE-1].cached;
|
||||
memmove(pool+k+1,pool+k,
|
||||
sizeof(pool[0])*(EVPOOL_SIZE-k-1));
|
||||
pool[k].cached = cached;
|
||||
} else {
|
||||
/* No free space on right? Insert at k-1 */
|
||||
k--;
|
||||
/* Shift all elements on the left of k (included) to the
|
||||
* left, so we discard the element with smaller idle time. */
|
||||
sds cached = pool[0].cached; /* Save SDS before overwriting. */
|
||||
if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
|
||||
memmove(pool,pool+1,sizeof(pool[0])*k);
|
||||
pool[k].cached = cached;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to reuse the cached SDS string allocated in the pool entry,
|
||||
* because allocating and deallocating this object is costly
|
||||
* (according to the profiler, not my fantasy. Remember:
|
||||
* premature optimizbla bla bla bla. */
|
||||
int klen = sdslen(key);
|
||||
if (klen > EVPOOL_CACHED_SDS_SIZE) {
|
||||
pool[k].key = sdsdup(key);
|
||||
} else {
|
||||
memcpy(pool[k].cached,key,klen+1);
|
||||
sdssetlen(pool[k].cached,klen);
|
||||
pool[k].key = pool[k].cached;
|
||||
}
|
||||
pool[k].idle = idle;
|
||||
pool[k].dbid = dbid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,8 +375,8 @@ size_t freeMemoryGetNotCountedMemory(void) {
|
||||
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = (client*)listNodeValue(ln);
|
||||
overhead += getClientOutputBufferMemoryUsage(slave);
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
overhead += getClientOutputBufferMemoryUsage(replica);
|
||||
}
|
||||
}
|
||||
if (g_pserver->aof_state != AOF_OFF) {
|
||||
@@ -474,8 +489,6 @@ int freeMemoryIfNeeded(void) {
|
||||
sds bestkey = NULL;
|
||||
int bestdbid;
|
||||
redisDb *db;
|
||||
dict *dict;
|
||||
dictEntry *de;
|
||||
|
||||
if (g_pserver->maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
|
||||
g_pserver->maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
|
||||
@@ -490,10 +503,18 @@ int freeMemoryIfNeeded(void) {
|
||||
* every DB. */
|
||||
for (i = 0; i < cserver.dbnum; i++) {
|
||||
db = g_pserver->db+i;
|
||||
dict = (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
|
||||
db->pdict : db->expires;
|
||||
if ((keys = dictSize(dict)) != 0) {
|
||||
evictionPoolPopulate(i, dict, db->pdict, pool);
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS)
|
||||
{
|
||||
if ((keys = db->size()) != 0) {
|
||||
evictionPoolPopulate(i, db, nullptr, pool);
|
||||
total_keys += keys;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
keys = db->expireSize();
|
||||
if (keys != 0)
|
||||
evictionPoolPopulate(i, db, db->setexpireUnsafe(), pool);
|
||||
total_keys += keys;
|
||||
}
|
||||
}
|
||||
@@ -503,14 +524,11 @@ int freeMemoryIfNeeded(void) {
|
||||
for (k = EVPOOL_SIZE-1; k >= 0; k--) {
|
||||
if (pool[k].key == NULL) continue;
|
||||
bestdbid = pool[k].dbid;
|
||||
sds key = nullptr;
|
||||
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
|
||||
de = dictFind(g_pserver->db[pool[k].dbid].pdict,
|
||||
pool[k].key);
|
||||
} else {
|
||||
de = dictFind(g_pserver->db[pool[k].dbid].expires,
|
||||
pool[k].key);
|
||||
}
|
||||
auto itr = g_pserver->db[pool[k].dbid].find(pool[k].key);
|
||||
if (itr != nullptr && (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS || itr.val()->FExpires()))
|
||||
key = itr.key();
|
||||
|
||||
/* Remove the entry from the pool. */
|
||||
if (pool[k].key != pool[k].cached)
|
||||
@@ -520,8 +538,8 @@ int freeMemoryIfNeeded(void) {
|
||||
|
||||
/* If the key exists, is our pick. Otherwise it is
|
||||
* a ghost and we need to try the next element. */
|
||||
if (de) {
|
||||
bestkey = (sds)dictGetKey(de);
|
||||
if (key) {
|
||||
bestkey = key;
|
||||
break;
|
||||
} else {
|
||||
/* Ghost... Iterate again. */
|
||||
@@ -540,13 +558,23 @@ int freeMemoryIfNeeded(void) {
|
||||
for (i = 0; i < cserver.dbnum; i++) {
|
||||
j = (++next_db) % cserver.dbnum;
|
||||
db = g_pserver->db+j;
|
||||
dict = (g_pserver->maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
|
||||
db->pdict : db->expires;
|
||||
if (dictSize(dict) != 0) {
|
||||
de = dictGetRandomKey(dict);
|
||||
bestkey = (sds)dictGetKey(de);
|
||||
bestdbid = j;
|
||||
break;
|
||||
if (g_pserver->maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM)
|
||||
{
|
||||
if (db->size() != 0) {
|
||||
auto itr = db->random();
|
||||
bestkey = itr.key();
|
||||
bestdbid = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (db->expireSize())
|
||||
{
|
||||
bestkey = (sds)db->random_expire().key();
|
||||
bestdbid = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,16 +582,17 @@ int freeMemoryIfNeeded(void) {
|
||||
/* Finally remove the selected key. */
|
||||
if (bestkey) {
|
||||
db = g_pserver->db+bestdbid;
|
||||
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
propagateExpire(db,keyobj,g_pserver->lazyfree_lazy_eviction);
|
||||
/* We compute the amount of memory freed by db*Delete() alone.
|
||||
* It is possible that actually the memory needed to propagate
|
||||
* the DEL in AOF and replication link is greater than the one
|
||||
* we are freeing removing the key, but we can't account for
|
||||
* that otherwise we would never exit the loop.
|
||||
*
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space. */
|
||||
* It is possible that actually the memory needed to propagate
|
||||
* the DEL in AOF and replication link is greater than the one
|
||||
* we are freeing removing the key, but we can't account for
|
||||
* that otherwise we would never exit the loop.
|
||||
*
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space. */
|
||||
delta = (long long) zmalloc_used_memory();
|
||||
latencyStartMonitor(eviction_latency);
|
||||
if (g_pserver->lazyfree_lazy_eviction)
|
||||
@@ -582,18 +611,18 @@ int freeMemoryIfNeeded(void) {
|
||||
keys_freed++;
|
||||
|
||||
/* When the memory to free starts to be big enough, we may
|
||||
* start spending so much time here that is impossible to
|
||||
* deliver data to the slaves fast enough, so we force the
|
||||
* transmission here inside the loop. */
|
||||
* start spending so much time here that is impossible to
|
||||
* deliver data to the slaves fast enough, so we force the
|
||||
* transmission here inside the loop. */
|
||||
if (slaves) flushSlavesOutputBuffers();
|
||||
|
||||
/* Normally our stop condition is the ability to release
|
||||
* a fixed, pre-computed amount of memory. However when we
|
||||
* are deleting objects in another thread, it's better to
|
||||
* check, from time to time, if we already reached our target
|
||||
* memory, since the "mem_freed" amount is computed only
|
||||
* across the dbAsyncDelete() call, while the thread can
|
||||
* release the memory all the time. */
|
||||
* a fixed, pre-computed amount of memory. However when we
|
||||
* are deleting objects in another thread, it's better to
|
||||
* check, from time to time, if we already reached our target
|
||||
* memory, since the "mem_freed" amount is computed only
|
||||
* across the dbAsyncDelete() call, while the thread can
|
||||
* release the memory all the time. */
|
||||
if (g_pserver->lazyfree_lazy_eviction && !(keys_freed % 16)) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
|
||||
/* Let's satisfy our stop condition. */
|
||||
|
||||
+246
-101
@@ -32,6 +32,21 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
propagateExpire(db,keyobj,g_pserver->lazyfree_lazy_expire);
|
||||
if (g_pserver->lazyfree_lazy_expire)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
if (g_pserver->tracking_clients) trackingInvalidateKey(keyobj);
|
||||
decrRefCount(keyobj);
|
||||
g_pserver->stat_expiredkeys++;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Incremental collection of expired keys.
|
||||
*
|
||||
@@ -51,27 +66,148 @@
|
||||
*
|
||||
* The parameter 'now' is the current time in milliseconds as is passed
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
long long t = dictGetSignedIntegerVal(de);
|
||||
if (now > t) {
|
||||
sds key = (sds)dictGetKey(de);
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
if (!e.FFat())
|
||||
{
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
|
||||
propagateExpire(db,keyobj,g_pserver->lazyfree_lazy_expire);
|
||||
if (g_pserver->lazyfree_lazy_expire)
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
expireEntryFat *pfat = e.pfatentry();
|
||||
robj *val = db->find(e.key());
|
||||
int deleted = 0;
|
||||
while (!pfat->FEmpty())
|
||||
{
|
||||
if (pfat->nextExpireEntry().when > now)
|
||||
break;
|
||||
|
||||
// Is it the full key expiration?
|
||||
if (pfat->nextExpireEntry().spsubkey == nullptr)
|
||||
{
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
if (setTypeRemove(val,pfat->nextExpireEntry().spsubkey.get())) {
|
||||
deleted++;
|
||||
if (setTypeSize(val) == 0) {
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OBJ_LIST:
|
||||
case OBJ_ZSET:
|
||||
case OBJ_HASH:
|
||||
default:
|
||||
serverAssert(false);
|
||||
}
|
||||
pfat->popfrontExpireEntry();
|
||||
}
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
robj objT;
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
initStaticStringObject(objT, (char*)e.key());
|
||||
signalModifiedKey(db,&objT);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"srem",&objT,db->id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pfat->FEmpty())
|
||||
{
|
||||
robj *keyobj = createStringObject(e.key(),sdslen(e.key()));
|
||||
removeExpire(db, keyobj);
|
||||
decrRefCount(keyobj);
|
||||
g_pserver->stat_expiredkeys++;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int parseUnitString(const char *sz)
|
||||
{
|
||||
if (strcasecmp(sz, "s") == 0)
|
||||
return UNIT_SECONDS;
|
||||
if (strcasecmp(sz, "ms") == 0)
|
||||
return UNIT_MILLISECONDS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, long long when, int unit)
|
||||
{
|
||||
switch (unit)
|
||||
{
|
||||
case UNIT_SECONDS:
|
||||
when *= 1000;
|
||||
case UNIT_MILLISECONDS:
|
||||
break;
|
||||
|
||||
default:
|
||||
addReplyError(c, "Invalid unit arg");
|
||||
return;
|
||||
}
|
||||
|
||||
when += basetime;
|
||||
|
||||
/* No key, return zero. */
|
||||
robj *val = lookupKeyWriteOrReply(c, key, shared.czero);
|
||||
if (val == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
if (!setTypeIsMember(val, szFromObj(subkey))) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
addReplyError(c, "object type is unsupported");
|
||||
return;
|
||||
}
|
||||
|
||||
setExpire(c, c->db, key, subkey, when);
|
||||
|
||||
addReply(c, shared.cone);
|
||||
}
|
||||
|
||||
void expireMemberCommand(client *c)
|
||||
{
|
||||
long long when;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc > 5) {
|
||||
addReplyError(c, "Invalid number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
int unit = UNIT_SECONDS;
|
||||
if (c->argc == 5) {
|
||||
unit = parseUnitString(szFromObj(c->argv[4]));
|
||||
}
|
||||
|
||||
expireMemberCore(c, c->argv[1], c->argv[2], mstime(), when, unit);
|
||||
}
|
||||
|
||||
void expireMemberAtCommand(client *c)
|
||||
{
|
||||
long long when;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_SECONDS);
|
||||
}
|
||||
|
||||
|
||||
/* Try to expire a few timed out keys. The algorithm used is adaptive and
|
||||
* will use few CPU cycles if there are few expiring keys, otherwise
|
||||
* it will get more aggressive to avoid that too much memory is used by
|
||||
@@ -147,7 +283,6 @@ void activeExpireCycle(int type) {
|
||||
long total_expired = 0;
|
||||
|
||||
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
||||
int expired;
|
||||
redisDb *db = g_pserver->db+(current_db % cserver.dbnum);
|
||||
|
||||
/* Increment the DB now so we are sure if we run out of time
|
||||
@@ -155,78 +290,46 @@ void activeExpireCycle(int type) {
|
||||
* distribute the time evenly across DBs. */
|
||||
current_db++;
|
||||
|
||||
/* Continue to expire if at the end of the cycle more than 25%
|
||||
* of the keys were expired. */
|
||||
do {
|
||||
unsigned long num, slots;
|
||||
long long now, ttl_sum;
|
||||
int ttl_samples;
|
||||
iteration++;
|
||||
long long now;
|
||||
iteration++;
|
||||
now = mstime();
|
||||
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if ((num = dictSize(db->expires)) == 0) {
|
||||
db->avg_ttl = 0;
|
||||
break;
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if (db->setexpireUnsafe()->empty())
|
||||
{
|
||||
db->avg_ttl = 0;
|
||||
db->last_expire_set = now;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t expired = 0;
|
||||
size_t tried = 0;
|
||||
long long check = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; // assume a check is roughly 1us. It isn't but good enough
|
||||
db->expireitr = db->setexpireUnsafe()->enumerate(db->expireitr, now, [&](expireEntry &e) __attribute__((always_inline)) {
|
||||
if (e.when() < now)
|
||||
{
|
||||
activeExpireCycleExpire(db, e, now);
|
||||
++expired;
|
||||
}
|
||||
slots = dictSlots(db->expires);
|
||||
now = mstime();
|
||||
++tried;
|
||||
|
||||
/* When there are less than 1% filled slots getting random
|
||||
* keys is expensive, so stop here waiting for better times...
|
||||
* The dictionary will be resized asap. */
|
||||
if (num && slots > DICT_HT_INITIAL_SIZE &&
|
||||
(num*100/slots < 1)) break;
|
||||
|
||||
/* The main collection cycle. Sample random keys among keys
|
||||
* with an expire set, checking for expired ones. */
|
||||
expired = 0;
|
||||
ttl_sum = 0;
|
||||
ttl_samples = 0;
|
||||
|
||||
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
|
||||
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
|
||||
|
||||
while (num--) {
|
||||
dictEntry *de;
|
||||
long long ttl;
|
||||
|
||||
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
|
||||
ttl = dictGetSignedIntegerVal(de)-now;
|
||||
if (activeExpireCycleTryExpire(db,de,now)) expired++;
|
||||
if (ttl > 0) {
|
||||
/* We want the average TTL of keys yet not expired. */
|
||||
ttl_sum += ttl;
|
||||
ttl_samples++;
|
||||
}
|
||||
total_sampled++;
|
||||
}
|
||||
total_expired += expired;
|
||||
|
||||
/* Update the average TTL stats for this database. */
|
||||
if (ttl_samples) {
|
||||
long long avg_ttl = ttl_sum/ttl_samples;
|
||||
|
||||
/* Do a simple running average with a few samples.
|
||||
* We just use the current estimate with a weight of 2%
|
||||
* and the previous estimate with a weight of 98%. */
|
||||
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
|
||||
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
|
||||
}
|
||||
|
||||
/* We can't block forever here even if there are many keys to
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
|
||||
if ((tried % ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) == 0)
|
||||
{
|
||||
/* We can't block forever here even if there are many keys to
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
elapsed = ustime()-start;
|
||||
if (elapsed > timelimit) {
|
||||
timelimit_exit = 1;
|
||||
g_pserver->stat_expired_time_cap_reached_count++;
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
check = ACTIVE_EXPIRE_CYCLE_FAST_DURATION;
|
||||
}
|
||||
/* We don't repeat the cycle if there are less than 25% of keys
|
||||
* found expired in the current DB. */
|
||||
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
|
||||
return true;
|
||||
}, &check);
|
||||
|
||||
total_expired += expired;
|
||||
}
|
||||
|
||||
elapsed = ustime()-start;
|
||||
@@ -248,17 +351,17 @@ void activeExpireCycle(int type) {
|
||||
*
|
||||
* Normally slaves do not process expires: they wait the masters to synthesize
|
||||
* DEL operations in order to retain consistency. However writable slaves are
|
||||
* an exception: if a key is created in the slave and an expire is assigned
|
||||
* an exception: if a key is created in the replica and an expire is assigned
|
||||
* to it, we need a way to expire such a key, since the master does not know
|
||||
* anything about such a key.
|
||||
*
|
||||
* In order to do so, we track keys created in the slave side with an expire
|
||||
* In order to do so, we track keys created in the replica side with an expire
|
||||
* set, and call the expireSlaveKeys() function from time to time in order to
|
||||
* reclaim the keys if they already expired.
|
||||
*
|
||||
* Note that the use case we are trying to cover here, is a popular one where
|
||||
* slaves are put in writable mode in order to compute slow operations in
|
||||
* the slave side that are mostly useful to actually read data in a more
|
||||
* the replica side that are mostly useful to actually read data in a more
|
||||
* processed way. Think at sets intersections in a tmp key, with an expire so
|
||||
* that it is also used as a cache to avoid intersecting every time.
|
||||
*
|
||||
@@ -267,7 +370,7 @@ void activeExpireCycle(int type) {
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* The dictionary where we remember key names and database ID of keys we may
|
||||
* want to expire from the slave. Since this function is not often used we
|
||||
* want to expire from the replica. Since this function is not often used we
|
||||
* don't even care to initialize the database at startup. We'll do it once
|
||||
* the feature is used the first time, that is, when rememberSlaveKeyWithExpire()
|
||||
* is called.
|
||||
@@ -300,20 +403,27 @@ void expireSlaveKeys(void) {
|
||||
while(dbids && dbid < cserver.dbnum) {
|
||||
if ((dbids & 1) != 0) {
|
||||
redisDb *db = g_pserver->db+dbid;
|
||||
dictEntry *expire = dictFind(db->expires,keyname);
|
||||
|
||||
// the expire is hashed based on the key pointer, so we need the point in the main db
|
||||
auto itrDB = db->find(keyname);
|
||||
auto itrExpire = db->setexpire()->end();
|
||||
if (itrDB != nullptr)
|
||||
itrExpire = db->setexpireUnsafe()->find(itrDB.key());
|
||||
int expired = 0;
|
||||
|
||||
if (expire &&
|
||||
activeExpireCycleTryExpire(g_pserver->db+dbid,expire,start))
|
||||
if (itrExpire != db->setexpire()->end())
|
||||
{
|
||||
expired = 1;
|
||||
if (itrExpire->when() < start) {
|
||||
activeExpireCycleExpire(g_pserver->db+dbid,*itrExpire,start);
|
||||
expired = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the key was not expired in this DB, we need to set the
|
||||
* corresponding bit in the new bitmap we set as value.
|
||||
* At the end of the loop if the bitmap is zero, it means we
|
||||
* no longer need to keep track of this key. */
|
||||
if (expire && !expired) {
|
||||
if (itrExpire != db->setexpire()->end() && !expired) {
|
||||
noexpire++;
|
||||
new_dbids |= (uint64_t)1 << dbid;
|
||||
}
|
||||
@@ -323,7 +433,7 @@ void expireSlaveKeys(void) {
|
||||
}
|
||||
|
||||
/* Set the new bitmap as value of the key, in the dictionary
|
||||
* of keys with an expire set directly in the writable slave. Otherwise
|
||||
* of keys with an expire set directly in the writable replica. Otherwise
|
||||
* if the bitmap is zero, we no longer need to keep track of it. */
|
||||
if (new_dbids)
|
||||
dictSetUnsignedIntegerVal(de,new_dbids);
|
||||
@@ -340,7 +450,7 @@ void expireSlaveKeys(void) {
|
||||
}
|
||||
|
||||
/* Track keys that received an EXPIRE or similar command in the context
|
||||
* of a writable slave. */
|
||||
* of a writable replica. */
|
||||
void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
|
||||
if (slaveKeysWithExpire == NULL) {
|
||||
static dictType dt = {
|
||||
@@ -382,7 +492,7 @@ size_t getSlaveKeyWithExpireCount(void) {
|
||||
*
|
||||
* Note: technically we should handle the case of a single DB being flushed
|
||||
* but it is not worth it since anyway race conditions using the same set
|
||||
* of key names in a wriatable slave and in its master will lead to
|
||||
* of key names in a wriatable replica and in its master will lead to
|
||||
* inconsistencies. This is just a best-effort thing we do. */
|
||||
void flushSlaveKeysWithExpireList(void) {
|
||||
if (slaveKeysWithExpire) {
|
||||
@@ -420,7 +530,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a slave instance.
|
||||
* of a replica instance.
|
||||
*
|
||||
* Instead we take the other branch of the IF statement setting an expire
|
||||
* (possibly in the past) and wait for an explicit DEL from the master. */
|
||||
@@ -440,7 +550,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
addReply(c, shared.cone);
|
||||
return;
|
||||
} else {
|
||||
setExpire(c,c->db,key,when);
|
||||
setExpire(c,c->db,key,nullptr,when);
|
||||
addReply(c,shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
|
||||
@@ -471,16 +581,40 @@ void pexpireatCommand(client *c) {
|
||||
|
||||
/* Implements TTL and PTTL */
|
||||
void ttlGenericCommand(client *c, int output_ms) {
|
||||
long long expire, ttl = -1;
|
||||
long long expire = -1, ttl = -1;
|
||||
|
||||
/* If the key does not exist at all, return -2 */
|
||||
if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == nullptr) {
|
||||
addReplyLongLong(c,-2);
|
||||
return;
|
||||
}
|
||||
|
||||
/* The key exists. Return -1 if it has no expire, or the actual
|
||||
* TTL value otherwise. */
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
* TTL value otherwise. */
|
||||
expireEntry *pexpire = c->db->getExpire(c->argv[1]);
|
||||
|
||||
if (c->argc == 2) {
|
||||
// primary expire
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expire);
|
||||
} else if (c->argc == 3) {
|
||||
// We want a subkey expire
|
||||
if (pexpire && pexpire->FFat()) {
|
||||
for (auto itr : *pexpire) {
|
||||
if (itr.subkey() == nullptr)
|
||||
continue;
|
||||
if (sdscmp((sds)itr.subkey(), szFromObj(c->argv[2])) == 0) {
|
||||
expire = itr.when();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addReplyError(c, "Invalid arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (expire != -1) {
|
||||
ttl = expire-mstime();
|
||||
if (ttl < 0) ttl = 0;
|
||||
@@ -505,11 +639,22 @@ void pttlCommand(client *c) {
|
||||
/* PERSIST key */
|
||||
void persistCommand(client *c) {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
if (c->argc == 2) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (c->argc == 3) {
|
||||
if (c->db->removeSubkeyExpire(c->argv[1], c->argv[2])) {
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
addReplyError(c, "Invalid arguments");
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
|
||||
@@ -46,13 +46,13 @@
|
||||
#define ECCENT (sqrt(1.0 - (RATIO *RATIO)))
|
||||
#define COM (0.5 * ECCENT)
|
||||
|
||||
/// @brief The usual PI/180 constant
|
||||
const double DEG_TO_RAD = 0.017453292519943295769236907684886;
|
||||
/// @brief Earth's quatratic mean radius for WGS-84
|
||||
const double EARTH_RADIUS_IN_METERS = 6372797.560856;
|
||||
|
||||
const double MERCATOR_MAX = 20037726.37;
|
||||
#if 0 //unused
|
||||
const double MERCATOR_MIN = -20037726.37;
|
||||
#endif
|
||||
|
||||
static inline double deg_rad(double ang) { return ang * D_R; }
|
||||
static inline double rad_deg(double ang) { return ang / D_R; }
|
||||
|
||||
+12
-6
@@ -343,6 +343,12 @@ struct commandHelp {
|
||||
"Set the expiration for a key as a UNIX timestamp",
|
||||
0,
|
||||
"1.2.0" },
|
||||
{ "EXPIREMEMBER",
|
||||
"key subkey delay [Unit: s,ms]",
|
||||
"set a subkey's time to live in seconds (or milliseconds)"},
|
||||
{ "EXPIREMEMBERAT",
|
||||
"key subkey timestamp",
|
||||
"Set the expiration for a subkey as a UNIX timestamp"},
|
||||
{ "FLUSHALL",
|
||||
"[ASYNC]",
|
||||
"Remove all keys from all databases",
|
||||
@@ -629,8 +635,8 @@ struct commandHelp {
|
||||
0,
|
||||
"2.2.3" },
|
||||
{ "PERSIST",
|
||||
"key",
|
||||
"Remove the expiration from a key",
|
||||
"key [subkey]",
|
||||
"Remove the expiration from a key or subkey",
|
||||
0,
|
||||
"2.2.0" },
|
||||
{ "PEXPIRE",
|
||||
@@ -674,8 +680,8 @@ struct commandHelp {
|
||||
6,
|
||||
"2.0.0" },
|
||||
{ "PTTL",
|
||||
"key",
|
||||
"Get the time to live for a key in milliseconds",
|
||||
"key [subkey]",
|
||||
"Get the time to live for a key or subkey in milliseconds",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "PUBLISH",
|
||||
@@ -949,8 +955,8 @@ struct commandHelp {
|
||||
0,
|
||||
"3.2.1" },
|
||||
{ "TTL",
|
||||
"key",
|
||||
"Get the time to live for a key",
|
||||
"key [subkey]",
|
||||
"Get the time to live for a key or subkey",
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "TYPE",
|
||||
|
||||
@@ -710,6 +710,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
first += span;
|
||||
}
|
||||
if (span == 0) return -1; /* Invalid format. */
|
||||
if (p >= end) return -1; /* Invalid format. */
|
||||
|
||||
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
|
||||
if (next >= end) next = NULL;
|
||||
|
||||
+31
-19
@@ -51,17 +51,20 @@ size_t lazyfreeGetFreeEffort(robj *obj) {
|
||||
* a lazy free list instead of being freed synchronously. The lazy free list
|
||||
* will be reclaimed in a different bio.c thread. */
|
||||
#define LAZYFREE_THRESHOLD 64
|
||||
int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
/* Deleting an entry from the expires dict will not free the sds of
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
if (dictSize(db->expires) > 0) dictDelete(db->expires,ptrFromObj(key));
|
||||
|
||||
bool redisDbPersistentData::asyncDelete(robj *key) {
|
||||
/* If the value is composed of a few allocations, to free in a lazy way
|
||||
* is actually just slower... So under a certain limit we just free
|
||||
* the object synchronously. */
|
||||
dictEntry *de = dictUnlink(db->pdict,ptrFromObj(key));
|
||||
dictEntry *de = dictUnlink(m_pdict,ptrFromObj(key));
|
||||
if (de) {
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
if (val->FExpires())
|
||||
{
|
||||
/* Deleting an entry from the expires dict will not free the sds of
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
removeExpire(key,dict_iter(de));
|
||||
}
|
||||
|
||||
size_t free_effort = lazyfreeGetFreeEffort(val);
|
||||
|
||||
/* If releasing the object is too much work, do it in the background
|
||||
@@ -72,28 +75,32 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
* objects, and then call dbDelete(). In this case we'll fall
|
||||
* through and reach the dictFreeUnlinkedEntry() call, that will be
|
||||
* equivalent to just calling decrRefCount(). */
|
||||
if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
|
||||
if (free_effort > LAZYFREE_THRESHOLD && val->getrefcount(std::memory_order_relaxed) == 1) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
|
||||
dictSetVal(db->pdict,de,NULL);
|
||||
dictSetVal(m_pdict,de,NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Release the key-val pair, or just the key if we set the val
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(db->pdict,de);
|
||||
dictFreeUnlinkedEntry(m_pdict,de);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(key);
|
||||
return 1;
|
||||
return true;
|
||||
} else {
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
return db->asyncDelete(key);
|
||||
}
|
||||
|
||||
/* Free an object, if the object is huge enough, free it in async way. */
|
||||
void freeObjAsync(robj *o) {
|
||||
size_t free_effort = lazyfreeGetFreeEffort(o);
|
||||
if (free_effort > LAZYFREE_THRESHOLD && o->refcount == 1) {
|
||||
if (free_effort > LAZYFREE_THRESHOLD && o->getrefcount(std::memory_order_relaxed) == 1) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,o,NULL,NULL);
|
||||
} else {
|
||||
@@ -104,12 +111,17 @@ void freeObjAsync(robj *o) {
|
||||
/* Empty a Redis DB asynchronously. What the function does actually is to
|
||||
* create a new empty set of hash tables and scheduling the old ones for
|
||||
* lazy freeing. */
|
||||
void emptyDbAsync(redisDb *db) {
|
||||
dict *oldht1 = db->pdict, *oldht2 = db->expires;
|
||||
db->pdict = dictCreate(&dbDictType,NULL);
|
||||
db->expires = dictCreate(&keyptrDictType,NULL);
|
||||
void redisDbPersistentData::emptyDbAsync() {
|
||||
dict *oldht1 = m_pdict;
|
||||
auto *set = m_setexpire;
|
||||
m_setexpire = new (MALLOC_LOCAL) expireset();
|
||||
m_pdict = dictCreate(&dbDictType,this);
|
||||
if (m_pstorage != nullptr)
|
||||
m_pstorage->clear();
|
||||
if (m_fTrackingChanges)
|
||||
m_fAllChanged = true;
|
||||
atomicIncr(lazyfree_objects,dictSize(oldht1));
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,set);
|
||||
}
|
||||
|
||||
/* Empty the slots-keys map of Redis CLuster by creating a new empty one
|
||||
@@ -136,10 +148,10 @@ void lazyfreeFreeObjectFromBioThread(robj *o) {
|
||||
* when the database was logically deleted. 'sl' is a skiplist used by
|
||||
* Redis Cluster in order to take the hash slots -> keys mapping. This
|
||||
* may be NULL if Redis Cluster is disabled. */
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) {
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, expireset *set) {
|
||||
size_t numkeys = dictSize(ht1);
|
||||
dictRelease(ht1);
|
||||
dictRelease(ht2);
|
||||
delete set;
|
||||
atomicDecr(lazyfree_objects,numkeys);
|
||||
}
|
||||
|
||||
|
||||
+38
-8
@@ -32,6 +32,7 @@
|
||||
#include <dlfcn.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define REDISMODULE_CORE 1
|
||||
#include "redismodule.h"
|
||||
@@ -565,7 +566,7 @@ void RedisModuleCommandDispatcher(client *c) {
|
||||
for (int i = 0; i < c->argc; i++) {
|
||||
/* Only do the work if the module took ownership of the object:
|
||||
* in that case the refcount is no longer 1. */
|
||||
if (c->argv[i]->refcount > 1)
|
||||
if (c->argv[i]->getrefcount(std::memory_order_relaxed) > 1)
|
||||
trimStringObjectIfNeeded(c->argv[i]);
|
||||
}
|
||||
}
|
||||
@@ -1036,7 +1037,7 @@ int RM_StringCompare(RedisModuleString *a, RedisModuleString *b) {
|
||||
/* Return the (possibly modified in encoding) input 'str' object if
|
||||
* the string is unshared, otherwise NULL is returned. */
|
||||
RedisModuleString *moduleAssertUnsharedString(RedisModuleString *str) {
|
||||
if (str->refcount != 1) {
|
||||
if (str->getrefcount(std::memory_order_relaxed) != 1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Module attempted to use an in-place string modify operation "
|
||||
"with a string referenced multiple times. Please check the code "
|
||||
@@ -1252,6 +1253,17 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Reply with a bulk string, taking in input a C buffer pointer that is
|
||||
* assumed to be null-terminated.
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithCString(RedisModuleCtx *ctx, const char *buf) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReplyBulkCString(c,(char*)buf);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Reply with a bulk string, taking in input a RedisModuleString object.
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
@@ -1427,7 +1439,7 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave
|
||||
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a replica
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only
|
||||
*
|
||||
@@ -1465,6 +1477,9 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
||||
if (g_pserver->cluster_enabled)
|
||||
flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
|
||||
|
||||
if (g_pserver->loading)
|
||||
flags |= REDISMODULE_CTX_FLAGS_LOADING;
|
||||
|
||||
/* Maxmemory and eviction policy */
|
||||
if (g_pserver->maxmemory > 0) {
|
||||
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
|
||||
@@ -1629,7 +1644,11 @@ int RM_UnlinkKey(RedisModuleKey *key) {
|
||||
* If no TTL is associated with the key or if the key is empty,
|
||||
* REDISMODULE_NO_EXPIRE is returned. */
|
||||
mstime_t RM_GetExpire(RedisModuleKey *key) {
|
||||
mstime_t expire = getExpire(key->db,key->key);
|
||||
expireEntry *pexpire = key->db->getExpire(key->key);
|
||||
mstime_t expire = -1;
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expire);
|
||||
|
||||
if (expire == -1 || key->value == NULL) return -1;
|
||||
expire -= mstime();
|
||||
return expire >= 0 ? expire : 0;
|
||||
@@ -1649,7 +1668,7 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) {
|
||||
return REDISMODULE_ERR;
|
||||
if (expire != REDISMODULE_NO_EXPIRE) {
|
||||
expire += mstime();
|
||||
setExpire(key->ctx->client,key->db,key->key,expire);
|
||||
setExpire(key->ctx->client,key->db,key->key,nullptr,expire);
|
||||
} else {
|
||||
removeExpire(key->db,key->key);
|
||||
}
|
||||
@@ -3920,7 +3939,9 @@ RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
|
||||
|
||||
/* Release a thread safe context. */
|
||||
void RM_FreeThreadSafeContext(RedisModuleCtx *ctx) {
|
||||
moduleAcquireGIL(false /*fServerThread*/);
|
||||
moduleFreeContext(ctx);
|
||||
moduleReleaseGIL(false /*fServerThread*/);
|
||||
zfree(ctx);
|
||||
}
|
||||
|
||||
@@ -4268,7 +4289,7 @@ size_t RM_GetClusterSize(void) {
|
||||
*
|
||||
* The arguments ip, master_id, port and flags can be NULL in case we don't
|
||||
* need to populate back certain info. If an ip and master_id (only populated
|
||||
* if the instance is a slave) are specified, they point to buffers holding
|
||||
* if the instance is a replica) are specified, they point to buffers holding
|
||||
* at least REDISMODULE_NODE_ID_LEN bytes. The strings written back as ip
|
||||
* and master_id are not null terminated.
|
||||
*
|
||||
@@ -4279,7 +4300,7 @@ size_t RM_GetClusterSize(void) {
|
||||
* * REDISMODULE_NODE_SLAVE The node is a replica
|
||||
* * REDISMODULE_NODE_PFAIL We see the node as failing
|
||||
* * REDISMODULE_NODE_FAIL The cluster agrees the node is failing
|
||||
* * REDISMODULE_NODE_NOFAILOVER The slave is configured to never failover
|
||||
* * REDISMODULE_NODE_NOFAILOVER The replica is configured to never failover
|
||||
*/
|
||||
|
||||
clusterNode *clusterLookupNode(const char *name); /* We need access to internals */
|
||||
@@ -5160,7 +5181,7 @@ void moduleInitModulesSystem(void) {
|
||||
* The function aborts the server on errors, since to start with missing
|
||||
* modules is not considered sane: clients may rely on the existence of
|
||||
* given commands, loading AOF also may need some modules to exist, and
|
||||
* if this instance is a slave, it must understand commands from master. */
|
||||
* if this instance is a replica, it must understand commands from master. */
|
||||
void moduleLoadFromQueue(void) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
@@ -5214,6 +5235,15 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
int (*onload)(void *, void **, int);
|
||||
void *handle;
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0)
|
||||
{ // this check is best effort
|
||||
if (!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
|
||||
serverLog(LL_WARNING, "Module %s failed to load: It does not have execute permissions.", path);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
handle = dlopen(path,RTLD_NOW|RTLD_LOCAL);
|
||||
if (handle == NULL) {
|
||||
|
||||
+3
-3
@@ -142,7 +142,7 @@ void execCommand(client *c) {
|
||||
}
|
||||
|
||||
/* If there are write commands inside the transaction, and this is a read
|
||||
* only slave, we want to send an error. This happens when the transaction
|
||||
* only replica, we want to send an error. This happens when the transaction
|
||||
* was initiated when the instance was a master or a writable replica and
|
||||
* then the configuration changed (for example instance was turned into
|
||||
* a replica). */
|
||||
@@ -195,7 +195,7 @@ void execCommand(client *c) {
|
||||
int is_master = listLength(g_pserver->masters) == 0;
|
||||
g_pserver->dirty++;
|
||||
/* If inside the MULTI/EXEC block this instance was suddenly
|
||||
* switched from master to slave (using the SLAVEOF command), the
|
||||
* switched from master to replica (using the SLAVEOF command), the
|
||||
* initial MULTI was propagated into the replication backlog, but the
|
||||
* rest was not. We need to make sure to at least terminate the
|
||||
* backlog with the final EXEC. */
|
||||
@@ -333,7 +333,7 @@ void touchWatchedKeysOnFlush(int dbid) {
|
||||
* key exists, mark the client as dirty, as the key will be
|
||||
* removed. */
|
||||
if (dbid == -1 || wk->db->id == dbid) {
|
||||
if (dictFind(wk->db->pdict, ptrFromObj(wk->key)) != NULL)
|
||||
if (wk->db->find(wk->key) != NULL)
|
||||
c->flags |= CLIENT_DIRTY_CAS;
|
||||
}
|
||||
}
|
||||
|
||||
+133
-48
@@ -174,6 +174,8 @@ client *createClient(int fd, int iel) {
|
||||
c->bufAsync = NULL;
|
||||
c->buflenAsync = 0;
|
||||
c->bufposAsync = 0;
|
||||
c->client_tracking_redirection = 0;
|
||||
c->casyncOpsPending = 0;
|
||||
memset(c->uuid, 0, UUID_BINARY_LEN);
|
||||
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
@@ -193,7 +195,7 @@ client *createClient(int fd, int iel) {
|
||||
* buffers can hold, then we'll really install the handler. */
|
||||
void clientInstallWriteHandler(client *c) {
|
||||
/* Schedule the client to write the output buffers to the socket only
|
||||
* if not already done and, for slaves, if the slave can actually receive
|
||||
* if not already done and, for slaves, if the replica can actually receive
|
||||
* writes at this stage. */
|
||||
if (!(c->flags & CLIENT_PENDING_WRITE) &&
|
||||
(c->replstate == REPL_STATE_NONE ||
|
||||
@@ -237,7 +239,7 @@ void clientInstallAsyncWriteHandler(client *c) {
|
||||
*
|
||||
* 1) The event handler should already be installed since the output buffer
|
||||
* already contains something.
|
||||
* 2) The client is a slave but not yet online, so we want to just accumulate
|
||||
* 2) The client is a replica but not yet online, so we want to just accumulate
|
||||
* writes in the buffer but not actually sending them yet.
|
||||
*
|
||||
* Typically gets called every time a reply is built, before adding more
|
||||
@@ -245,7 +247,7 @@ void clientInstallAsyncWriteHandler(client *c) {
|
||||
* data should be appended to the output buffers. */
|
||||
int prepareClientToWrite(client *c, bool fAsync) {
|
||||
fAsync = fAsync && !FCorrectThread(c); // Not async if we're on the right thread
|
||||
serverAssert(!fAsync || GlobalLocksAcquired());
|
||||
serverAssert(FCorrectThread(c) || fAsync);
|
||||
serverAssert(c->fd <= 0 || c->lock.fOwnLock());
|
||||
|
||||
if (c->flags & CLIENT_FORCE_REPLY) return C_OK; // FORCE REPLY means we're doing something else with the buffer.
|
||||
@@ -440,7 +442,7 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
|
||||
addReplyProtoCore(c,s,len,fAsync);
|
||||
addReplyProtoCore(c,"\r\n",2,fAsync);
|
||||
|
||||
/* Sometimes it could be normal that a slave replies to a master with
|
||||
/* Sometimes it could be normal that a replica replies to a master with
|
||||
* an error and this function gets called. Actually the error will never
|
||||
* be sent because addReply*() against master clients has no effect...
|
||||
* A notable example is:
|
||||
@@ -1003,7 +1005,6 @@ static void acceptCommonHandler(int fd, int flags, char *ip, int iel) {
|
||||
serverLog(LL_WARNING,
|
||||
"Error registering fd event for the new client: %s (fd=%d)",
|
||||
strerror(errno),fd);
|
||||
close(fd); /* May be already closed, just ignore errors */
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1213,7 +1214,7 @@ void unlinkClient(client *c) {
|
||||
|
||||
/* In the case of diskless replication the fork is writing to the
|
||||
* sockets and just closing the fd isn't enough, if we don't also
|
||||
* shutdown the socket the fork will continue to write to the slave
|
||||
* shutdown the socket the fork will continue to write to the replica
|
||||
* and the salve will only find out that it was disconnected when
|
||||
* it will finish reading the rdb. */
|
||||
if ((c->flags & CLIENT_SLAVE) &&
|
||||
@@ -1264,9 +1265,12 @@ void unlinkClient(client *c) {
|
||||
serverAssert(fFound);
|
||||
c->fPendingAsyncWrite = FALSE;
|
||||
}
|
||||
|
||||
/* Clear the tracking status. */
|
||||
if (c->flags & CLIENT_TRACKING) disableTracking(c);
|
||||
}
|
||||
|
||||
void freeClient(client *c) {
|
||||
bool freeClient(client *c) {
|
||||
listNode *ln;
|
||||
serverAssert(c->fd == -1 || GlobalLocksAcquired());
|
||||
AssertCorrectThread(c);
|
||||
@@ -1274,9 +1278,9 @@ void freeClient(client *c) {
|
||||
|
||||
/* If a client is protected, yet we need to free it right now, make sure
|
||||
* to at least use asynchronous freeing. */
|
||||
if (c->flags & CLIENT_PROTECTED) {
|
||||
if (c->flags & CLIENT_PROTECTED || c->casyncOpsPending) {
|
||||
freeClientAsync(c);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If it is our master that's beging disconnected we should make sure
|
||||
@@ -1291,11 +1295,11 @@ void freeClient(client *c) {
|
||||
CLIENT_BLOCKED)))
|
||||
{
|
||||
replicationCacheMaster(MasterInfoFromClient(c), c);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Log link disconnection with slave */
|
||||
/* Log link disconnection with replica */
|
||||
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
|
||||
serverLog(LL_WARNING,"Connection with replica %s lost.",
|
||||
replicationGetSlaveName(c));
|
||||
@@ -1329,8 +1333,8 @@ void freeClient(client *c) {
|
||||
* places where active clients may be referenced. */
|
||||
unlinkClient(c);
|
||||
|
||||
/* Master/slave cleanup Case 1:
|
||||
* we lost the connection with a slave. */
|
||||
/* Master/replica cleanup Case 1:
|
||||
* we lost the connection with a replica. */
|
||||
if (c->flags & CLIENT_SLAVE) {
|
||||
if (c->replstate == SLAVE_STATE_SEND_BULK) {
|
||||
if (c->repldbfd != -1) close(c->repldbfd);
|
||||
@@ -1348,7 +1352,7 @@ void freeClient(client *c) {
|
||||
refreshGoodSlavesCount();
|
||||
}
|
||||
|
||||
/* Master/slave cleanup Case 2:
|
||||
/* Master/replica cleanup Case 2:
|
||||
* we lost the connection with the master. */
|
||||
if (c->flags & CLIENT_MASTER) replicationHandleMasterDisconnection(MasterInfoFromClient(c));
|
||||
|
||||
@@ -1370,6 +1374,7 @@ void freeClient(client *c) {
|
||||
ulock.unlock();
|
||||
fastlock_free(&c->lock);
|
||||
zfree(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Schedule a client to free it at a safe time in the serverCron() function.
|
||||
@@ -1382,28 +1387,37 @@ void freeClientAsync(client *c) {
|
||||
* may access the list while Redis uses I/O threads. All the other accesses
|
||||
* are in the context of the main thread while the other threads are
|
||||
* idle. */
|
||||
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return;
|
||||
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return; // check without the lock first
|
||||
std::lock_guard<decltype(c->lock)> clientlock(c->lock);
|
||||
AeLocker lock;
|
||||
lock.arm(c);
|
||||
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return; // race condition after we acquire the lock
|
||||
c->flags |= CLIENT_CLOSE_ASAP;
|
||||
listAddNodeTail(g_pserver->clients_to_close,c);
|
||||
}
|
||||
|
||||
void freeClientsInAsyncFreeQueue(int iel) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(g_pserver->clients_to_close,&li);
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
// Store the clients in a temp vector since freeClient will modify this list
|
||||
std::vector<client*> vecclientsFree;
|
||||
while((ln = listNext(&li)))
|
||||
{
|
||||
client *c = (client*)listNodeValue(ln);
|
||||
if (c->iel != iel)
|
||||
continue; // wrong thread
|
||||
if (c->iel == iel)
|
||||
{
|
||||
vecclientsFree.push_back(c);
|
||||
listDelNode(g_pserver->clients_to_close, ln);
|
||||
}
|
||||
}
|
||||
|
||||
for (client *c : vecclientsFree)
|
||||
{
|
||||
c->flags &= ~CLIENT_CLOSE_ASAP;
|
||||
freeClient(c);
|
||||
listDelNode(g_pserver->clients_to_close,ln);
|
||||
listRewind(g_pserver->clients_to_close,&li);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1480,7 +1494,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
* just deliver as much data as it is possible to deliver.
|
||||
*
|
||||
* Moreover, we also send as much as possible if the client is
|
||||
* a slave (otherwise, on high-speed traffic, the replication
|
||||
* a replica (otherwise, on high-speed traffic, the replication
|
||||
* buffer will grow indefinitely) */
|
||||
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
|
||||
(g_pserver->maxmemory == 0 ||
|
||||
@@ -1551,6 +1565,15 @@ void ProcessPendingAsyncWrites()
|
||||
std::lock_guard<decltype(c->lock)> lock(c->lock);
|
||||
|
||||
serverAssert(c->fPendingAsyncWrite);
|
||||
if (c->flags & (CLIENT_CLOSE_ASAP | CLIENT_CLOSE_AFTER_REPLY))
|
||||
{
|
||||
c->bufposAsync = 0;
|
||||
c->buflenAsync = 0;
|
||||
zfree(c->bufAsync);
|
||||
c->bufAsync = nullptr;
|
||||
c->fPendingAsyncWrite = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Append to end of reply block?
|
||||
|
||||
@@ -1587,8 +1610,36 @@ void ProcessPendingAsyncWrites()
|
||||
continue;
|
||||
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
if (aeCreateRemoteFileEvent(g_pserver->rgthreadvar[c->iel].el, c->fd, ae_flags, sendReplyToClient, c, FALSE) == AE_ERR)
|
||||
continue; // We can retry later in the cron
|
||||
if (c->flags & CLIENT_CLOSE_ASAP)
|
||||
continue; // we will never write this so don't post an op
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
|
||||
if (c->casyncOpsPending == 0)
|
||||
{
|
||||
if (FCorrectThread(c))
|
||||
{
|
||||
prepareClientToWrite(c, false); // queue an event
|
||||
}
|
||||
else
|
||||
{
|
||||
// We need to start the write on the client's thread
|
||||
if (aePostFunction(g_pserver->rgthreadvar[c->iel].el, [c]{
|
||||
// Install a write handler. Don't do the actual write here since we don't want
|
||||
// to duplicate the throttling and safety mechanisms of the normal write code
|
||||
std::lock_guard<decltype(c->lock)> lock(c->lock);
|
||||
serverAssert(c->casyncOpsPending > 0);
|
||||
c->casyncOpsPending--;
|
||||
aeCreateFileEvent(g_pserver->rgthreadvar[c->iel].el, c->fd, AE_WRITABLE|AE_WRITE_THREADSAFE, sendReplyToClient, c);
|
||||
}, false) == AE_ERR
|
||||
)
|
||||
{
|
||||
// Posting the function failed
|
||||
continue; // We can retry later in the cron
|
||||
}
|
||||
++c->casyncOpsPending; // race is handled by the client lock in the lambda
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1628,13 +1679,15 @@ int handleClientsWithPendingWrites(int iel) {
|
||||
std::unique_lock<decltype(c->lock)> lock(c->lock);
|
||||
|
||||
/* Try to write buffers to the client socket. */
|
||||
if (writeToClient(c->fd,c,0) == C_ERR) {
|
||||
if (writeToClient(c->fd,c,0) == C_ERR)
|
||||
{
|
||||
if (c->flags & CLIENT_CLOSE_ASAP)
|
||||
{
|
||||
lock.release(); // still locked
|
||||
AeLocker ae;
|
||||
ae.arm(c);
|
||||
freeClient(c); // writeToClient will only async close, but there's no need to wait
|
||||
if (!freeClient(c)) // writeToClient will only async close, but there's no need to wait
|
||||
c->lock.unlock(); // if we just got put on the async close list, then we need to remove the lock
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -1752,7 +1805,7 @@ int processInlineBuffer(client *c) {
|
||||
}
|
||||
|
||||
/* Newline from slaves can be used to refresh the last ACK time.
|
||||
* This is useful for a slave to ping back while loading a big
|
||||
* This is useful for a replica to ping back while loading a big
|
||||
* RDB file. */
|
||||
if (querylen == 0 && c->flags & CLIENT_SLAVE)
|
||||
c->repl_ack_time = g_pserver->unixtime;
|
||||
@@ -1985,8 +2038,8 @@ int processCommandAndResetClient(client *c, int flags) {
|
||||
}
|
||||
if (serverTL->current_client == NULL) deadclient = 1;
|
||||
serverTL->current_client = NULL;
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may
|
||||
* result into a slave, that may be the active client, to be
|
||||
/* freeMemoryIfNeeded may flush replica output buffers. This may
|
||||
* result into a replica, that may be the active client, to be
|
||||
* freed. */
|
||||
return deadclient ? C_ERR : C_OK;
|
||||
}
|
||||
@@ -2007,7 +2060,7 @@ void processInputBuffer(client *c, int callFlags) {
|
||||
if (c->flags & CLIENT_BLOCKED) break;
|
||||
|
||||
/* Don't process input from the master while there is a busy script
|
||||
* condition on the slave. We want just to accumulate the replication
|
||||
* condition on the replica. We want just to accumulate the replication
|
||||
* stream (instead of replying -BUSY like we do with other clients) and
|
||||
* later resume the processing. */
|
||||
if (g_pserver->lua_timedout && c->flags & CLIENT_MASTER) break;
|
||||
@@ -2245,6 +2298,8 @@ sds catClientInfoString(sds s, client *client) {
|
||||
if (client->flags & CLIENT_PUBSUB) *p++ = 'P';
|
||||
if (client->flags & CLIENT_MULTI) *p++ = 'x';
|
||||
if (client->flags & CLIENT_BLOCKED) *p++ = 'b';
|
||||
if (client->flags & CLIENT_TRACKING) *p++ = 't';
|
||||
if (client->flags & CLIENT_TRACKING_BROKEN_REDIR) *p++ = 'R';
|
||||
if (client->flags & CLIENT_DIRTY_CAS) *p++ = 'd';
|
||||
if (client->flags & CLIENT_CLOSE_AFTER_REPLY) *p++ = 'c';
|
||||
if (client->flags & CLIENT_UNBLOCKED) *p++ = 'u';
|
||||
@@ -2357,6 +2412,7 @@ void clientCommand(client *c) {
|
||||
"reply (on|off|skip) -- Control the replies sent to the current connection.",
|
||||
"setname <name> -- Assign the name <name> to the current connection.",
|
||||
"unblock <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
|
||||
"tracking (on|off) [REDIRECT <id>] -- Enable client keys tracking for client side caching.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@@ -2515,21 +2571,57 @@ NULL
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[1]),"setname") && c->argc == 3) {
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"setname") && c->argc == 3) {
|
||||
/* CLIENT SETNAME */
|
||||
if (clientSetNameOrReply(c,c->argv[2]) == C_OK)
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[1]),"getname") && c->argc == 2) {
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"getname") && c->argc == 2) {
|
||||
/* CLIENT GETNAME */
|
||||
if (c->name)
|
||||
addReplyBulk(c,c->name);
|
||||
else
|
||||
addReplyNull(c, shared.nullbulk);
|
||||
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[1]),"pause") && c->argc == 3) {
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"pause") && c->argc == 3) {
|
||||
/* CLIENT PAUSE */
|
||||
long long duration;
|
||||
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,UNIT_MILLISECONDS)
|
||||
!= C_OK) return;
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,
|
||||
UNIT_MILLISECONDS) != C_OK) return;
|
||||
pauseClients(duration);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"tracking") &&
|
||||
(c->argc == 3 || c->argc == 5))
|
||||
{
|
||||
/* CLIENT TRACKING (on|off) [REDIRECT <id>] */
|
||||
long long redir = 0;
|
||||
|
||||
/* Parse the redirection option: we'll require the client with
|
||||
* the specified ID to exist right now, even if it is possible
|
||||
* it will get disconnected later. */
|
||||
if (c->argc == 5) {
|
||||
if (strcasecmp(szFromObj(c->argv[3]),"redirect") != 0) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
} else {
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[4],&redir,NULL) !=
|
||||
C_OK) return;
|
||||
if (lookupClientByID(redir) == NULL) {
|
||||
addReplyError(c,"The client ID you want redirect to "
|
||||
"does not exist");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcasecmp(szFromObj(c->argv[2]),"on")) {
|
||||
enableTracking(c,redir);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[2]),"off")) {
|
||||
disableTracking(c);
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLIENT HELP", (char*)ptrFromObj(c->argv[1]));
|
||||
}
|
||||
@@ -2696,15 +2788,8 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function returns the number of bytes that Redis is virtually
|
||||
/* This function returns the number of bytes that Redis is
|
||||
* using to store the reply still not read by the client.
|
||||
* It is "virtual" since the reply output list may contain objects that
|
||||
* are shared and are not really using additional memory.
|
||||
*
|
||||
* The function returns the total sum of the length of all the objects
|
||||
* stored in the output list, plus the memory used to allocate every
|
||||
* list node. The static reply buffer is not taken into account since it
|
||||
* is allocated anyway.
|
||||
*
|
||||
* Note: this function is very fast so can be called as many time as
|
||||
* the caller wishes. The main usage of this function currently is
|
||||
@@ -2825,10 +2910,10 @@ void flushSlavesOutputBuffers(void) {
|
||||
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = (client*)listNodeValue(ln);
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
int events;
|
||||
|
||||
if (!FCorrectThread(slave))
|
||||
if (!FCorrectThread(replica))
|
||||
continue; // we cannot synchronously flush other thread's clients
|
||||
|
||||
/* Note that the following will not flush output buffers of slaves
|
||||
@@ -2837,12 +2922,12 @@ void flushSlavesOutputBuffers(void) {
|
||||
* of put_online_on_ack is to postpone the moment it is installed.
|
||||
* This is what we want since slaves in this state should not receive
|
||||
* writes before the first ACK. */
|
||||
events = aeGetFileEvents(g_pserver->rgthreadvar[slave->iel].el,slave->fd);
|
||||
events = aeGetFileEvents(g_pserver->rgthreadvar[replica->iel].el,replica->fd);
|
||||
if (events & AE_WRITABLE &&
|
||||
slave->replstate == SLAVE_STATE_ONLINE &&
|
||||
clientHasPendingReplies(slave))
|
||||
replica->replstate == SLAVE_STATE_ONLINE &&
|
||||
clientHasPendingReplies(replica))
|
||||
{
|
||||
writeToClient(slave->fd,slave,0);
|
||||
writeToClient(replica->fd,replica,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+164
-38
@@ -39,11 +39,11 @@
|
||||
/* ===================== Creation and parsing of objects ==================== */
|
||||
|
||||
robj *createObject(int type, void *ptr) {
|
||||
robj *o = (robj*)zmalloc(sizeof(*o), MALLOC_SHARED);
|
||||
robj *o = (robj*)zcalloc(sizeof(*o), MALLOC_SHARED);
|
||||
o->type = type;
|
||||
o->encoding = OBJ_ENCODING_RAW;
|
||||
o->m_ptr = ptr;
|
||||
o->refcount.store(1, std::memory_order_relaxed);
|
||||
o->setrefcount(1);
|
||||
o->mvcc_tstamp = OBJ_MVCC_INVALID;
|
||||
|
||||
/* Set the LRU to the current lruclock (minutes resolution), or
|
||||
@@ -68,8 +68,9 @@ robj *createObject(int type, void *ptr) {
|
||||
*
|
||||
*/
|
||||
robj *makeObjectShared(robj *o) {
|
||||
serverAssert(o->refcount == 1);
|
||||
o->refcount.store(OBJ_SHARED_REFCOUNT, std::memory_order_relaxed);
|
||||
serverAssert(o->getrefcount(std::memory_order_relaxed) == 1);
|
||||
serverAssert(!o->FExpires());
|
||||
o->setrefcount(OBJ_SHARED_REFCOUNT);
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -86,12 +87,12 @@ robj *createEmbeddedStringObject(const char *ptr, size_t len) {
|
||||
size_t allocsize = sizeof(struct sdshdr8)+len+1;
|
||||
if (allocsize < sizeof(void*))
|
||||
allocsize = sizeof(void*);
|
||||
robj *o = (robj*)zmalloc(sizeof(robj)+allocsize-sizeof(o->m_ptr), MALLOC_SHARED);
|
||||
robj *o = (robj*)zcalloc(sizeof(robj)+allocsize-sizeof(o->m_ptr), MALLOC_SHARED);
|
||||
struct sdshdr8 *sh = (sdshdr8*)(&o->m_ptr);
|
||||
|
||||
o->type = OBJ_STRING;
|
||||
o->encoding = OBJ_ENCODING_EMBSTR;
|
||||
o->refcount.store(1, std::memory_order_relaxed);
|
||||
o->setrefcount(1);
|
||||
o->mvcc_tstamp = OBJ_MVCC_INVALID;
|
||||
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
@@ -352,11 +353,14 @@ void freeStreamObject(robj_roptr o) {
|
||||
}
|
||||
|
||||
void incrRefCount(robj_roptr o) {
|
||||
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount.fetch_add(1, std::memory_order_acquire);
|
||||
if (o->getrefcount(std::memory_order_relaxed) != OBJ_SHARED_REFCOUNT) o->addref();
|
||||
}
|
||||
|
||||
void decrRefCount(robj_roptr o) {
|
||||
if (o->refcount.load(std::memory_order_acquire) == 1) {
|
||||
if (o->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
|
||||
return;
|
||||
unsigned prev = o->release();
|
||||
if (prev == 1) {
|
||||
switch(o->type) {
|
||||
case OBJ_STRING: freeStringObject(o); break;
|
||||
case OBJ_LIST: freeListObject(o); break;
|
||||
@@ -369,8 +373,7 @@ void decrRefCount(robj_roptr o) {
|
||||
}
|
||||
zfree(o.unsafe_robjcast());
|
||||
} else {
|
||||
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
|
||||
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount.fetch_sub(1, std::memory_order_acquire);
|
||||
if (prev <= 0) serverPanic("decrRefCount against refcount <= 0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +397,7 @@ void decrRefCountVoid(const void *o) {
|
||||
* decrRefCount(obj);
|
||||
*/
|
||||
robj *resetRefCount(robj *obj) {
|
||||
obj->refcount = 0;
|
||||
obj->setrefcount(0);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -452,7 +455,7 @@ robj *tryObjectEncoding(robj *o) {
|
||||
/* It's not safe to encode shared objects: shared objects can be shared
|
||||
* everywhere in the "object space" of Redis and may end in places where
|
||||
* they are not handled. We handle them only as values in the keyspace. */
|
||||
if (o->refcount > 1) return o;
|
||||
if (o->getrefcount(std::memory_order_relaxed) > 1) return o;
|
||||
|
||||
/* Check if we can represent this string as a long integer.
|
||||
* Note that we are sure that a string larger than 20 chars is not
|
||||
@@ -786,7 +789,7 @@ size_t streamRadixTreeMemoryUsage(rax *rax) {
|
||||
* case of aggregated data types where only "sample_size" elements
|
||||
* are checked and averaged to estimate the total size. */
|
||||
#define OBJ_COMPUTE_SIZE_DEF_SAMPLES 5 /* Default sample size. */
|
||||
size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
size_t objectComputeSize(robj_roptr o, size_t sample_size) {
|
||||
sds ele, ele2;
|
||||
dict *d;
|
||||
dictIterator *di;
|
||||
@@ -797,7 +800,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
if(o->encoding == OBJ_ENCODING_INT) {
|
||||
asize = sizeof(*o);
|
||||
} else if(o->encoding == OBJ_ENCODING_RAW) {
|
||||
asize = sdsAllocSize(szFromObj(o))+sizeof(*o);
|
||||
asize = sdsAllocSize((sds)szFromObj(o))+sizeof(*o);
|
||||
} else if(o->encoding == OBJ_ENCODING_EMBSTR) {
|
||||
asize = sdslen(szFromObj(o))+2+sizeof(*o);
|
||||
} else {
|
||||
@@ -1051,21 +1054,20 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
redisDb *db = g_pserver->db+j;
|
||||
long long keyscount = dictSize(db->pdict);
|
||||
long long keyscount = db->size();
|
||||
if (keyscount==0) continue;
|
||||
|
||||
mh->total_keys += keyscount;
|
||||
mh->db = (decltype(mh->db))zrealloc(mh->db,sizeof(mh->db[0])*(mh->num_dbs+1), MALLOC_LOCAL);
|
||||
mh->db[mh->num_dbs].dbid = j;
|
||||
|
||||
mem = dictSize(db->pdict) * sizeof(dictEntry) +
|
||||
dictSlots(db->pdict) * sizeof(dictEntry*) +
|
||||
dictSize(db->pdict) * sizeof(robj);
|
||||
mem = db->size() * sizeof(dictEntry) +
|
||||
db->slots() * sizeof(dictEntry*) +
|
||||
db->size() * sizeof(robj);
|
||||
mh->db[mh->num_dbs].overhead_ht_main = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = dictSize(db->expires) * sizeof(dictEntry) +
|
||||
dictSlots(db->expires) * sizeof(dictEntry*);
|
||||
mem = db->setexpire()->bytes_used();
|
||||
mh->db[mh->num_dbs].overhead_ht_expires = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
@@ -1244,15 +1246,12 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
|
||||
/* This is a helper function for the OBJECT command. We need to lookup keys
|
||||
* without any modification of LRU or other parameters. */
|
||||
robj *objectCommandLookup(client *c, robj *key) {
|
||||
dictEntry *de;
|
||||
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(key))) == NULL) return NULL;
|
||||
return (robj*) dictGetVal(de);
|
||||
robj_roptr objectCommandLookup(client *c, robj *key) {
|
||||
return c->db->find(key);
|
||||
}
|
||||
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
robj *o = objectCommandLookup(c,key);
|
||||
robj_roptr objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
robj_roptr o = objectCommandLookup(c,key);
|
||||
|
||||
if (!o) addReply(c, reply);
|
||||
return o;
|
||||
@@ -1261,7 +1260,7 @@ robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
/* Object command allows to inspect the internals of an Redis Object.
|
||||
* Usage: OBJECT <refcount|encoding|idletime|freq> <key> */
|
||||
void objectCommand(client *c) {
|
||||
robj *o;
|
||||
robj_roptr o;
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
|
||||
const char *help[] = {
|
||||
@@ -1274,15 +1273,15 @@ NULL
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->refcount);
|
||||
== nullptr) return;
|
||||
addReplyLongLong(c,o->getrefcount(std::memory_order_relaxed));
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"encoding") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
== nullptr) return;
|
||||
addReplyBulkCString(c,strEncoding(o->encoding));
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"idletime") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
== nullptr) return;
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
@@ -1290,7 +1289,7 @@ NULL
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
== nullptr) return;
|
||||
if (!(g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
@@ -1299,7 +1298,11 @@ NULL
|
||||
* in case of the key has not been accessed for a long time,
|
||||
* because we update the access time only
|
||||
* when the key is read or overwritten. */
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o));
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o.unsafe_robjcast()));
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]), "lastmodified") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== nullptr) return;
|
||||
addReplyLongLong(c, (g_pserver->mstime - (o->mvcc_tstamp >> MVCC_MS_SHIFT)) / 1000);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
@@ -1321,7 +1324,6 @@ NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"usage") && c->argc >= 3) {
|
||||
dictEntry *de;
|
||||
long long samples = OBJ_COMPUTE_SIZE_DEF_SAMPLES;
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
if (!strcasecmp(szFromObj(c->argv[j]),"samples") &&
|
||||
@@ -1340,12 +1342,14 @@ NULL
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(c->argv[2]))) == NULL) {
|
||||
|
||||
auto itr = c->db->find(c->argv[2]);
|
||||
if (itr == nullptr) {
|
||||
addReplyNull(c, shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
size_t usage = objectComputeSize((robj*)dictGetVal(de),samples);
|
||||
usage += sdsAllocSize((sds)dictGetKey(de));
|
||||
size_t usage = objectComputeSize(itr.val(),samples);
|
||||
usage += sdsAllocSize(itr.key());
|
||||
usage += sizeof(dictEntry);
|
||||
addReplyLongLong(c,usage);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stats") && c->argc == 2) {
|
||||
@@ -1474,3 +1478,125 @@ NULL
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)ptrFromObj(c->argv[1]));
|
||||
}
|
||||
}
|
||||
|
||||
void redisObject::SetFExpires(bool fExpire)
|
||||
{
|
||||
serverAssert(this->refcount != OBJ_SHARED_REFCOUNT);
|
||||
if (fExpire)
|
||||
this->refcount.fetch_or(1U << 31, std::memory_order_relaxed);
|
||||
else
|
||||
this->refcount.fetch_and(~(1U << 31), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void redisObject::setrefcount(unsigned ref)
|
||||
{
|
||||
serverAssert(!FExpires());
|
||||
refcount.store(ref, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
sds serializeStoredStringObject(sds str, robj_roptr o)
|
||||
{
|
||||
str = sdscatlen(str, &(*o), sizeof(robj));
|
||||
switch (o->encoding)
|
||||
{
|
||||
case OBJ_ENCODING_RAW:
|
||||
str = sdscatsds(str, (sds)szFromObj(o));
|
||||
break;
|
||||
|
||||
case OBJ_ENCODING_INT:
|
||||
break; //nop
|
||||
|
||||
case OBJ_ENCODING_EMBSTR:
|
||||
size_t cb = zmalloc_size(o.unsafe_robjcast());
|
||||
if (cb > sizeof(robj))
|
||||
{
|
||||
str = sdscatlen(str, o.unsafe_robjcast() + 1, cb - sizeof(robj));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
robj *deserializeStoredStringObject(const char *data, size_t cb)
|
||||
{
|
||||
const robj *oT = (const robj*)data;
|
||||
robj *newObject = nullptr;
|
||||
switch (oT->encoding)
|
||||
{
|
||||
case OBJ_ENCODING_INT:
|
||||
serverAssert(cb == sizeof(robj));
|
||||
[[fallthrough]];
|
||||
case OBJ_ENCODING_EMBSTR:
|
||||
newObject = (robj*)zmalloc(cb, MALLOC_LOCAL);
|
||||
memcpy(newObject, data, cb);
|
||||
newObject->setrefcount(1);
|
||||
return newObject;
|
||||
|
||||
case OBJ_ENCODING_RAW:
|
||||
newObject = (robj*)zmalloc(sizeof(robj), MALLOC_SHARED);
|
||||
memcpy(newObject, data, sizeof(robj));
|
||||
newObject->m_ptr = sdsnewlen(SDS_NOINIT,cb-sizeof(robj));
|
||||
memcpy(newObject->m_ptr, data+sizeof(robj), cb-sizeof(robj));
|
||||
newObject->setrefcount(1);
|
||||
return newObject;
|
||||
}
|
||||
serverPanic("Unknown string object encoding from storage");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
robj *deserializeStoredObject(const void *data, size_t cb)
|
||||
{
|
||||
switch (((char*)data)[0])
|
||||
{
|
||||
case RDB_TYPE_STRING:
|
||||
return deserializeStoredStringObject(((char*)data)+1, cb-1);
|
||||
|
||||
default:
|
||||
rio payload;
|
||||
int type;
|
||||
robj *obj;
|
||||
rioInitWithConstBuffer(&payload,data,cb);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,nullptr, OBJ_MVCC_INVALID)) == nullptr))
|
||||
{
|
||||
serverPanic("Bad data format");
|
||||
}
|
||||
if (rdbLoadType(&payload) == RDB_OPCODE_AUX)
|
||||
{
|
||||
robj *auxkey, *auxval;
|
||||
if ((auxkey = rdbLoadStringObject(&payload)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(&payload)) == NULL) {
|
||||
decrRefCount(auxkey);
|
||||
goto eoferr;
|
||||
}
|
||||
if (strcasecmp(szFromObj(auxkey), "mvcc-tstamp") == 0) {
|
||||
obj->mvcc_tstamp = strtoull(szFromObj(auxval), nullptr, 10);
|
||||
}
|
||||
decrRefCount(auxkey);
|
||||
decrRefCount(auxval);
|
||||
}
|
||||
eoferr:
|
||||
return obj;
|
||||
}
|
||||
serverPanic("Unknown object type loading from storage");
|
||||
}
|
||||
|
||||
sds serializeStoredObject(robj_roptr o)
|
||||
{
|
||||
switch (o->type)
|
||||
{
|
||||
case OBJ_STRING:
|
||||
{
|
||||
sds sdsT = sdsnewlen(nullptr, 1);
|
||||
sdsT[0] = RDB_TYPE_STRING;
|
||||
return serializeStoredStringObject(sdsT, o);
|
||||
}
|
||||
|
||||
default:
|
||||
rio rdb;
|
||||
createDumpPayload(&rdb,o,nullptr);
|
||||
return (sds)rdb.io.buffer.ptr;
|
||||
}
|
||||
serverPanic("Attempting to store unknown object type");
|
||||
}
|
||||
@@ -143,6 +143,8 @@ int clientSubscriptionsCount(client *c) {
|
||||
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was already subscribed to that channel. */
|
||||
int pubsubSubscribeChannel(client *c, robj *channel) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
serverAssert(c->lock.fOwnLock());
|
||||
dictEntry *de;
|
||||
list *clients = NULL;
|
||||
int retval = 0;
|
||||
@@ -202,6 +204,7 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
|
||||
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
|
||||
int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
int retval = 0;
|
||||
|
||||
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
|
||||
@@ -244,6 +247,7 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
/* Unsubscribe from all the channels. Return the number of channels the
|
||||
* client was subscribed to. */
|
||||
int pubsubUnsubscribeAllChannels(client *c, int notify) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
|
||||
dictEntry *de;
|
||||
int count = 0;
|
||||
@@ -262,6 +266,7 @@ int pubsubUnsubscribeAllChannels(client *c, int notify) {
|
||||
/* Unsubscribe from all the patterns. Return the number of patterns the
|
||||
* client was subscribed from. */
|
||||
int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
int count = 0;
|
||||
@@ -278,6 +283,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
|
||||
/* Publish a message */
|
||||
int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
int receivers = 0;
|
||||
dictEntry *de;
|
||||
listNode *ln;
|
||||
@@ -293,6 +299,8 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
listRewind(list,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = reinterpret_cast<client*>(ln->value);
|
||||
if (c->flags & CLIENT_CLOSE_ASAP) // avoid blocking if the write will be ignored
|
||||
continue;
|
||||
fastlock_lock(&c->lock);
|
||||
addReplyPubsubMessage(c,channel,message);
|
||||
fastlock_unlock(&c->lock);
|
||||
@@ -311,6 +319,8 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
(char*)ptrFromObj(channel),
|
||||
sdslen(szFromObj(channel)),0))
|
||||
{
|
||||
if (pat->pclient->flags & CLIENT_CLOSE_ASAP)
|
||||
continue;
|
||||
fastlock_lock(&pat->pclient->lock);
|
||||
addReplyPubsubPatMessage(pat->pclient,
|
||||
pat->pattern,channel,message);
|
||||
|
||||
@@ -705,7 +705,7 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
nodesize = sizeof(raxNode)+trimmedlen+raxPadding(trimmedlen)+
|
||||
sizeof(raxNode*);
|
||||
if (h->iskey && !h->isnull) nodesize += sizeof(void*);
|
||||
trimmed = rax_malloc(nodesize);
|
||||
trimmed = zcalloc(nodesize, MALLOC_LOCAL);//rax_malloc(nodesize);
|
||||
}
|
||||
|
||||
if (postfixlen) {
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ extern "C" {
|
||||
#include <sys/wait.h>
|
||||
|
||||
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
|
||||
int rdbSaveS3(char *s3bucket, rdbSaveInfo *rsi)
|
||||
int rdbSaveS3(char *s3bucket, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi)
|
||||
{
|
||||
int status = EXIT_FAILURE;
|
||||
int fd[2];
|
||||
@@ -40,7 +40,7 @@ int rdbSaveS3(char *s3bucket, rdbSaveInfo *rsi)
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (rdbSaveFp(fp, rsi) != C_OK)
|
||||
if (rdbSaveFp(fp, rgpdb, rsi) != C_OK)
|
||||
{
|
||||
fclose(fp);
|
||||
return C_ERR;
|
||||
|
||||
+398
-250
File diff suppressed because it is too large.
Load diff
@@ -136,13 +136,14 @@ int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
||||
int rdbSaveObjectType(rio *rdb, robj_roptr o);
|
||||
int rdbLoadObjectType(rio *rdb);
|
||||
int rdbLoad(rdbSaveInfo *rsi);
|
||||
int rdbLoadFile(const char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveBackground(rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(rdbSaveInfo *rsi);
|
||||
int rdbSaveFile(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveFp(FILE *pf, rdbSaveInfo *rsi);
|
||||
int rdbSaveS3(char *path, rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(int childpid);
|
||||
int rdbSave(redisDbPersistentData **rgpdb, rdbSaveInfo *rsi);
|
||||
int rdbSaveFile(char *filename, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi);
|
||||
int rdbSaveFp(FILE *pf, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi);
|
||||
int rdbSaveS3(char *path, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi);
|
||||
int rdbLoadS3(char *path, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj_roptr o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
|
||||
@@ -152,7 +152,7 @@ typedef struct clusterNode {
|
||||
int port;
|
||||
sds name;
|
||||
int flags;
|
||||
sds replicate; /* Master ID if node is a slave */
|
||||
sds replicate; /* Master ID if node is a replica */
|
||||
int *slots;
|
||||
int slots_count;
|
||||
int current_slot_index;
|
||||
@@ -1546,7 +1546,10 @@ int main(int argc, const char **argv) {
|
||||
if (node->name) printf("%s ", node->name);
|
||||
printf("%s:%d\n", node->ip, node->port);
|
||||
node->redis_config = getRedisConfig(node->ip, node->port, NULL);
|
||||
if (node->redis_config == NULL) exit(1);
|
||||
if (node->redis_config == NULL) {
|
||||
fprintf(stderr, "WARN: could not fetch node CONFIG %s:%d\n",
|
||||
node->ip, node->port);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
/* Automatically set thread number to node count if not specified
|
||||
@@ -1556,7 +1559,8 @@ int main(int argc, const char **argv) {
|
||||
} else {
|
||||
config.redis_config =
|
||||
getRedisConfig(config.hostip, config.hostport, config.hostsocket);
|
||||
if (config.redis_config == NULL) exit(1);
|
||||
if (config.redis_config == NULL)
|
||||
fprintf(stderr, "WARN: could not fetch server CONFIG\n");
|
||||
}
|
||||
|
||||
if (config.num_threads > 0) {
|
||||
|
||||
@@ -124,8 +124,8 @@ extern "C" void freeClusterManager(void) {
|
||||
*
|
||||
* The score is calculated as follows:
|
||||
*
|
||||
* SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
|
||||
* SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
|
||||
* SAME_AS_MASTER = 10000 * each replica in the same IP of its master.
|
||||
* SAME_AS_SLAVE = 1 * each replica having the same IP as another replica
|
||||
of the same master.
|
||||
* FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
|
||||
*
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ typedef struct clusterManagerNode {
|
||||
time_t ping_recv;
|
||||
int flags;
|
||||
list *flags_str; /* Flags string representations */
|
||||
sds replicate; /* Master ID if node is a slave */
|
||||
sds replicate; /* Master ID if node is a replica */
|
||||
int dirty; /* Node has changes that can be flushed */
|
||||
uint8_t slots[CLUSTER_MANAGER_SLOTS];
|
||||
int slots_count;
|
||||
|
||||
+6
-2
@@ -71,9 +71,9 @@ extern "C" {
|
||||
#define REDISMODULE_CTX_FLAGS_MULTI (1<<1)
|
||||
/* The instance is a master */
|
||||
#define REDISMODULE_CTX_FLAGS_MASTER (1<<2)
|
||||
/* The instance is a slave */
|
||||
/* The instance is a replica */
|
||||
#define REDISMODULE_CTX_FLAGS_SLAVE (1<<3)
|
||||
/* The instance is read-only (usually meaning it's a slave as well) */
|
||||
/* The instance is read-only (usually meaning it's a replica as well) */
|
||||
#define REDISMODULE_CTX_FLAGS_READONLY (1<<4)
|
||||
/* The instance is running in cluster mode */
|
||||
#define REDISMODULE_CTX_FLAGS_CLUSTER (1<<5)
|
||||
@@ -91,6 +91,8 @@ extern "C" {
|
||||
#define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11)
|
||||
/* The command was sent over the replication link. */
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12)
|
||||
/* Redis is currently loading either from AOF or RDB. */
|
||||
#define REDISMODULE_CTX_FLAGS_LOADING (1<<13)
|
||||
|
||||
|
||||
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
||||
@@ -230,6 +232,7 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx,
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
||||
@@ -380,6 +383,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(ReplyWithArray);
|
||||
REDISMODULE_GET_API(ReplySetArrayLength);
|
||||
REDISMODULE_GET_API(ReplyWithStringBuffer);
|
||||
REDISMODULE_GET_API(ReplyWithCString);
|
||||
REDISMODULE_GET_API(ReplyWithString);
|
||||
REDISMODULE_GET_API(ReplyWithNull);
|
||||
REDISMODULE_GET_API(ReplyWithCallReply);
|
||||
|
||||
+317
-243
File diff suppressed because it is too large.
Load diff
+28
@@ -73,6 +73,14 @@ static size_t rioBufferRead(rio *r, void *buf, size_t len) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t rioConstBufferRead(rio *r, void *buf, size_t len) {
|
||||
if (r->io.buffer.len-r->io.buffer.pos < (off_t)len)
|
||||
return 0; /* not enough buffer to return len bytes. */
|
||||
memcpy(buf,r->io.buffer.ptr+r->io.buffer.pos,len);
|
||||
r->io.buffer.pos += len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Returns read/write position in buffer. */
|
||||
static off_t rioBufferTell(rio *r) {
|
||||
return r->io.buffer.pos;
|
||||
@@ -97,12 +105,32 @@ static const rio rioBufferIO = {
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
static const rio rioConstBufferIO = {
|
||||
rioConstBufferRead,
|
||||
nullptr,
|
||||
rioBufferTell,
|
||||
rioBufferFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithBuffer(rio *r, sds s) {
|
||||
*r = rioBufferIO;
|
||||
r->io.buffer.ptr = s;
|
||||
r->io.buffer.pos = 0;
|
||||
}
|
||||
|
||||
void rioInitWithConstBuffer(rio *r, const void *buf, size_t cb)
|
||||
{
|
||||
*r = rioConstBufferIO;
|
||||
r->io.buffer.ptr = (sds)buf;
|
||||
r->io.buffer.pos = 0;
|
||||
r->io.buffer.len = cb;
|
||||
}
|
||||
|
||||
/* --------------------- Stdio file pointer implementation ------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
|
||||
@@ -70,6 +70,7 @@ struct _rio {
|
||||
struct {
|
||||
sds ptr;
|
||||
off_t pos;
|
||||
off_t len; // For const buffers only
|
||||
} buffer;
|
||||
/* Stdio file pointer target. */
|
||||
struct {
|
||||
@@ -130,6 +131,7 @@ static inline int rioFlush(rio *r) {
|
||||
|
||||
void rioInitWithFile(rio *r, FILE *fp);
|
||||
void rioInitWithBuffer(rio *r, sds s);
|
||||
void rioInitWithConstBuffer(rio *r, const void *rgch, size_t cch);
|
||||
void rioInitWithFdset(rio *r, int *fds, int numfds);
|
||||
|
||||
void rioFreeFdset(rio *r);
|
||||
|
||||
+2
-2
@@ -665,7 +665,7 @@ cleanup:
|
||||
* The object must be small, SDS-encoded, and with refcount = 1
|
||||
* (we must be the only owner) for us to cache it. */
|
||||
if (j < LUA_CMD_OBJCACHE_SIZE &&
|
||||
o->refcount == 1 &&
|
||||
o->getrefcount(std::memory_order_relaxed) == 1 &&
|
||||
(o->encoding == OBJ_ENCODING_RAW ||
|
||||
o->encoding == OBJ_ENCODING_EMBSTR) &&
|
||||
sdslen((sds)ptrFromObj(o)) <= LUA_CMD_OBJCACHE_MAX_LEN)
|
||||
@@ -1479,7 +1479,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
* To do so we use a cache of SHA1s of scripts that we already propagated
|
||||
* as full EVAL, that's called the Replication Script Cache.
|
||||
*
|
||||
* For repliation, everytime a new slave attaches to the master, we need to
|
||||
* For repliation, everytime a new replica attaches to the master, we need to
|
||||
* flush our cache of scripts that can be replicated as EVALSHA, while
|
||||
* for AOF we need to do so every time we rewrite the AOF file. */
|
||||
if (evalsha && !g_pserver->lua_replicate_commands) {
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
#pragma once
|
||||
#include <assert.h>
|
||||
#include "compactvector.h"
|
||||
|
||||
/****************************************
|
||||
* semiorderedset.h:
|
||||
*
|
||||
* The ordered set is a hash set that maintains semi-ordering, that is you can iterate in sub-linear time over the set comparing a value.
|
||||
* It has a few other useful properties vs the traditional set:
|
||||
* 1. The key need not be the underlying type, the only requirement is the value type is castable to the key
|
||||
* 2. The key need not have total ordering. The set will iterate until it finds an exact match with operator== on the value
|
||||
* This provides additional flexibility on insert allowing us to optimize this case.
|
||||
*
|
||||
*/
|
||||
|
||||
extern uint64_t dictGenHashFunction(const void *key, int len);
|
||||
|
||||
template<typename T, typename T_KEY = T, bool MEMMOVE_SAFE = false>
|
||||
class semiorderedset
|
||||
{
|
||||
friend struct setiter;
|
||||
std::vector<compactvector<T, MEMMOVE_SAFE>> m_data;
|
||||
size_t celem = 0;
|
||||
static const size_t bits_min = 8;
|
||||
size_t bits = bits_min;
|
||||
size_t idxRehash = (1ULL << bits_min);
|
||||
bool fPauseRehash = false;
|
||||
|
||||
constexpr size_t targetElementsPerBucket()
|
||||
{
|
||||
// Aim for roughly 4 cache lines per bucket (determined by imperical testing)
|
||||
// lower values are faster but use more memory
|
||||
return std::max((64/sizeof(T))*8, (size_t)2);
|
||||
}
|
||||
|
||||
public:
|
||||
semiorderedset()
|
||||
{
|
||||
m_data.resize((1ULL << bits));
|
||||
}
|
||||
|
||||
struct setiter
|
||||
{
|
||||
semiorderedset *set;
|
||||
size_t idxPrimary = 0;
|
||||
size_t idxSecondary = 0;
|
||||
|
||||
setiter(semiorderedset *set)
|
||||
{
|
||||
this->set = set;
|
||||
}
|
||||
|
||||
bool operator==(const setiter &other) const
|
||||
{
|
||||
return (idxPrimary == other.idxPrimary) && (idxSecondary == other.idxSecondary);
|
||||
}
|
||||
|
||||
bool operator!=(const setiter &other) const { return !operator==(other); }
|
||||
|
||||
inline T &operator*() { return set->m_data[idxPrimary][idxSecondary]; }
|
||||
inline const T &operator*() const { return set->m_data[idxPrimary][idxSecondary]; }
|
||||
|
||||
inline T *operator->() { return &set->m_data[idxPrimary][idxSecondary]; }
|
||||
inline const T *operator->() const { return &set->m_data[idxPrimary][idxSecondary]; }
|
||||
};
|
||||
|
||||
setiter find(const T_KEY &key)
|
||||
{
|
||||
RehashStep();
|
||||
setiter itr(this);
|
||||
itr.idxPrimary = idxFromObj(key);
|
||||
|
||||
for (int hashset = 0; hashset < 2; ++hashset) // rehashing may only be 1 resize behind, so we check up to two slots
|
||||
{
|
||||
auto &vecBucket = m_data[itr.idxPrimary];
|
||||
|
||||
auto itrFind = std::find(vecBucket.begin(), vecBucket.end(), key);
|
||||
if (itrFind != vecBucket.end())
|
||||
{
|
||||
itr.idxSecondary = itrFind - vecBucket.begin();
|
||||
return itr;
|
||||
}
|
||||
|
||||
// See if we have to check the older slot
|
||||
size_t mask = (hashmask() >> 1);
|
||||
if (itr.idxPrimary == (itr.idxPrimary & mask))
|
||||
break; // same bucket we just checked
|
||||
itr.idxPrimary &= mask;
|
||||
if (FRehashedRow(itr.idxPrimary))
|
||||
break;
|
||||
}
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
setiter end() const
|
||||
{
|
||||
setiter itr(const_cast<semiorderedset<T,T_KEY,MEMMOVE_SAFE>*>(this));
|
||||
itr.idxPrimary = m_data.size();
|
||||
return itr;
|
||||
}
|
||||
|
||||
void insert(T &e, bool fRehash = false)
|
||||
{
|
||||
if (!fRehash)
|
||||
RehashStep();
|
||||
|
||||
auto idx = idxFromObj(static_cast<T_KEY>(e));
|
||||
if (!fRehash)
|
||||
++celem;
|
||||
|
||||
typename compactvector<T, MEMMOVE_SAFE>::iterator itrInsert;
|
||||
if (!m_data[idx].empty() && !(e < m_data[idx].back()))
|
||||
itrInsert = m_data[idx].end();
|
||||
else
|
||||
itrInsert = std::upper_bound(m_data[idx].begin(), m_data[idx].end(), e);
|
||||
itrInsert = m_data[idx].insert(itrInsert, e);
|
||||
|
||||
if (celem > ((1ULL << bits)*targetElementsPerBucket()))
|
||||
grow();
|
||||
}
|
||||
|
||||
// enumeration starting from the 'itrStart'th key. Note that the iter is a hint, and need no be valid anymore
|
||||
template<typename T_VISITOR, typename T_MAX>
|
||||
setiter enumerate(const setiter &itrStart, const T_MAX &max, T_VISITOR fn, long long *pccheck)
|
||||
{
|
||||
setiter itr(itrStart);
|
||||
|
||||
if (itrStart.set == this) // really if this case isn't true its probably a bug
|
||||
itr = itrStart; // but why crash the program when we can easily fix this?
|
||||
|
||||
fPauseRehash = true;
|
||||
if (itr.idxPrimary >= m_data.size())
|
||||
itr.idxPrimary = 0;
|
||||
|
||||
for (size_t ibucket = 0; ibucket < m_data.size(); ++ibucket)
|
||||
{
|
||||
if (!enumerate_bucket(itr, max, fn, pccheck))
|
||||
break;
|
||||
itr.idxSecondary = 0;
|
||||
|
||||
++itr.idxPrimary;
|
||||
if (itr.idxPrimary >= m_data.size())
|
||||
itr.idxPrimary = 0;
|
||||
}
|
||||
fPauseRehash = false;
|
||||
return itr;
|
||||
}
|
||||
|
||||
// This will "randomly" visit nodes biased towards lower values first
|
||||
template<typename T_VISITOR>
|
||||
size_t random_visit(T_VISITOR &fn)
|
||||
{
|
||||
bool fSawAny = true;
|
||||
size_t visited = 0;
|
||||
size_t basePrimary = rand() % m_data.size();
|
||||
for (size_t idxSecondary = 0; fSawAny; ++idxSecondary)
|
||||
{
|
||||
fSawAny = false;
|
||||
for (size_t idxPrimaryCount = 0; idxPrimaryCount < m_data.size(); ++idxPrimaryCount)
|
||||
{
|
||||
size_t idxPrimary = (basePrimary + idxPrimaryCount) % m_data.size();
|
||||
if (idxSecondary < m_data[idxPrimary].size())
|
||||
{
|
||||
++visited;
|
||||
fSawAny = true;
|
||||
if (!fn(m_data[idxPrimary][idxSecondary]))
|
||||
return visited;
|
||||
}
|
||||
}
|
||||
}
|
||||
return visited;
|
||||
}
|
||||
|
||||
const T& random_value() const
|
||||
{
|
||||
assert(!empty());
|
||||
for (;;)
|
||||
{
|
||||
size_t idxPrimary = rand() % m_data.size();
|
||||
if (m_data[idxPrimary].empty())
|
||||
continue;
|
||||
|
||||
return m_data[idxPrimary][rand() % m_data[idxPrimary].size()];
|
||||
}
|
||||
}
|
||||
|
||||
void erase(const setiter &itr)
|
||||
{
|
||||
auto &vecRow = m_data[itr.idxPrimary];
|
||||
vecRow.erase(vecRow.begin() + itr.idxSecondary);
|
||||
--celem;
|
||||
RehashStep();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_data = decltype(m_data)();
|
||||
bits = bits_min;
|
||||
m_data.resize(1ULL << bits);
|
||||
idxRehash = m_data.size();
|
||||
}
|
||||
|
||||
bool empty() const noexcept { return celem == 0; }
|
||||
size_t size() const noexcept { return celem; }
|
||||
|
||||
size_t bytes_used() const
|
||||
{
|
||||
size_t cb = sizeof(this) + (m_data.capacity()-m_data.size())*sizeof(T);
|
||||
for (auto &vec : m_data)
|
||||
{
|
||||
cb += vec.bytes_used();
|
||||
}
|
||||
return cb;
|
||||
}
|
||||
|
||||
#define DICT_STATS_VECTLEN 50
|
||||
size_t getstats(char *buf, size_t bufsize) const
|
||||
{
|
||||
unsigned long i, slots = 0, chainlen, maxchainlen = 0;
|
||||
unsigned long totchainlen = 0;
|
||||
unsigned long clvector[DICT_STATS_VECTLEN] = {0};
|
||||
size_t l = 0;
|
||||
|
||||
if (empty()) {
|
||||
return snprintf(buf,bufsize,
|
||||
"No stats available for empty dictionaries\n");
|
||||
}
|
||||
|
||||
/* Compute stats. */
|
||||
for (auto &vec : m_data) {
|
||||
if (vec.empty()) {
|
||||
clvector[0]++;
|
||||
continue;
|
||||
}
|
||||
slots++;
|
||||
/* For each hash entry on this slot... */
|
||||
chainlen = vec.size();
|
||||
|
||||
clvector[(chainlen < DICT_STATS_VECTLEN) ? chainlen : (DICT_STATS_VECTLEN-1)]++;
|
||||
if (chainlen > maxchainlen) maxchainlen = chainlen;
|
||||
totchainlen += chainlen;
|
||||
}
|
||||
|
||||
size_t used = m_data.size()-clvector[0];
|
||||
/* Generate human readable stats. */
|
||||
l += snprintf(buf+l,bufsize-l,
|
||||
"semiordered set stats:\n"
|
||||
" table size: %ld\n"
|
||||
" number of slots: %ld\n"
|
||||
" used slots: %ld\n"
|
||||
" max chain length: %ld\n"
|
||||
" avg chain length (counted): %.02f\n"
|
||||
" avg chain length (computed): %.02f\n"
|
||||
" Chain length distribution:\n",
|
||||
size(), used, slots, maxchainlen,
|
||||
(float)totchainlen/slots, (float)size()/m_data.size());
|
||||
|
||||
for (i = 0; i < DICT_STATS_VECTLEN; i++) {
|
||||
if (clvector[i] == 0) continue;
|
||||
if (l >= bufsize) break;
|
||||
l += snprintf(buf+l,bufsize-l,
|
||||
" %s%ld: %ld (%.02f%%)\n",
|
||||
(i == DICT_STATS_VECTLEN-1)?">= ":"",
|
||||
i, clvector[i], ((float)clvector[i]/m_data.size())*100);
|
||||
}
|
||||
|
||||
/* Unlike snprintf(), teturn the number of characters actually written. */
|
||||
if (bufsize) buf[bufsize-1] = '\0';
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
private:
|
||||
inline size_t hashmask() const { return (1ULL << bits) - 1; }
|
||||
|
||||
size_t idxFromObj(const T_KEY &key)
|
||||
{
|
||||
size_t v = (size_t)dictGenHashFunction(&key, sizeof(key));
|
||||
return v & hashmask();
|
||||
}
|
||||
|
||||
bool FRehashedRow(size_t idx) const
|
||||
{
|
||||
return (idx >= (m_data.size()/2)) || (idx < idxRehash);
|
||||
}
|
||||
|
||||
void RehashStep()
|
||||
{
|
||||
if (fPauseRehash)
|
||||
return;
|
||||
|
||||
int steps = 0;
|
||||
for (; idxRehash < (m_data.size()/2); ++idxRehash)
|
||||
{
|
||||
compactvector<T, MEMMOVE_SAFE> vecT;
|
||||
std::swap(m_data[idxRehash], vecT);
|
||||
|
||||
for (auto &v : vecT)
|
||||
insert(v, true);
|
||||
|
||||
if (++steps > 1024)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void grow()
|
||||
{
|
||||
assert(idxRehash >= (m_data.size()/2)); // we should have finished rehashing by the time we need to grow again
|
||||
|
||||
++bits;
|
||||
m_data.resize(1ULL << bits);
|
||||
idxRehash = 0;
|
||||
RehashStep();
|
||||
}
|
||||
|
||||
template<typename T_VISITOR, typename T_MAX>
|
||||
inline bool enumerate_bucket(setiter &itr, const T_MAX &max, T_VISITOR &fn, long long *pcheckLimit)
|
||||
{
|
||||
auto &vec = m_data[itr.idxPrimary];
|
||||
for (; itr.idxSecondary < vec.size(); ++itr.idxSecondary)
|
||||
{
|
||||
// Assert we're ordered by T_MAX
|
||||
assert((itr.idxSecondary+1) >= vec.size()
|
||||
|| static_cast<T_MAX>(vec[itr.idxSecondary]) <= static_cast<T_MAX>(vec[itr.idxSecondary+1]));
|
||||
|
||||
(*pcheckLimit)--;
|
||||
if (max < static_cast<T_MAX>(*itr))
|
||||
return *pcheckLimit > 0;
|
||||
|
||||
size_t sizeBefore = vec.size();
|
||||
if (!fn(*itr))
|
||||
{
|
||||
itr.idxSecondary++; // we still visited this node
|
||||
return false;
|
||||
}
|
||||
if (vec.size() != sizeBefore)
|
||||
{
|
||||
assert(vec.size() == (sizeBefore-1)); // they may only remove the element passed to them
|
||||
--itr.idxSecondary; // they deleted the element
|
||||
}
|
||||
}
|
||||
vec.shrink_to_fit();
|
||||
return *pcheckLimit > 0;
|
||||
}
|
||||
};
|
||||
+165
-81
@@ -59,6 +59,7 @@
|
||||
#include <sys/socket.h>
|
||||
#include <algorithm>
|
||||
#include <uuid/uuid.h>
|
||||
#include <mutex>
|
||||
#include "aelocker.h"
|
||||
|
||||
int g_fTestMode = false;
|
||||
@@ -153,7 +154,7 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
|
||||
*
|
||||
* ok-loading: Allow the command while loading the database.
|
||||
*
|
||||
* ok-stale: Allow the command while a slave has stale data but is not
|
||||
* ok-stale: Allow the command while a replica has stale data but is not
|
||||
* allowed to serve this data. Normally no command is accepted
|
||||
* in this condition but just a few.
|
||||
*
|
||||
@@ -228,6 +229,10 @@ struct redisCommand redisCommandTable[] = {
|
||||
"write @keyspace",
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
|
||||
{"expdel",delCommand,-2,
|
||||
"write @keyspace",
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
|
||||
{"unlink",unlinkCommand,-2,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
@@ -618,6 +623,14 @@ struct redisCommand redisCommandTable[] = {
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"expiremember", expireMemberCommand, -4,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"expirememberat", expireMemberAtCommand, 4,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"pexpire",pexpireCommand,3,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
@@ -721,7 +734,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
"admin no-script",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"ttl",ttlCommand,2,
|
||||
{"ttl",ttlCommand,-2,
|
||||
"read-only fast random @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
@@ -729,11 +742,11 @@ struct redisCommand redisCommandTable[] = {
|
||||
"read-only fast @keyspace",
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
|
||||
{"pttl",pttlCommand,2,
|
||||
{"pttl",pttlCommand,-2,
|
||||
"read-only fast random @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"persist",persistCommand,2,
|
||||
{"persist",persistCommand,-2,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
@@ -1166,6 +1179,21 @@ int dictSdsKeyCompare(void *privdata, const void *key1,
|
||||
return memcmp(key1, key2, l1) == 0;
|
||||
}
|
||||
|
||||
void dictSdsNOPDestructor(void *, void *) {}
|
||||
|
||||
void dictDbKeyDestructor(void *privdata, void *key)
|
||||
{
|
||||
redisDbPersistentData *owner = (redisDbPersistentData*)privdata;
|
||||
serverAssert(owner != nullptr);
|
||||
if (owner->m_spdbSnapshot != nullptr)
|
||||
{
|
||||
dictEntry *deSnapshot = dictFind(owner->m_spdbSnapshot->m_pdict, key);
|
||||
if (deSnapshot && (key == dictGetKey(deSnapshot)))
|
||||
return; // don't free, it's now owned by the snapshot
|
||||
}
|
||||
sdsfree((sds)key);
|
||||
}
|
||||
|
||||
/* A case insensitive version used for the command lookup table and other
|
||||
* places where case insensitive non binary-safe comparison is needed. */
|
||||
int dictSdsKeyCaseCompare(void *privdata, const void *key1,
|
||||
@@ -1301,10 +1329,19 @@ dictType dbDictType = {
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
dictDbKeyDestructor, /* key destructor */
|
||||
dictObjectDestructor /* val destructor */
|
||||
};
|
||||
|
||||
dictType dbSnapshotDictType = {
|
||||
dictSdsHash,
|
||||
NULL,
|
||||
NULL,
|
||||
dictSdsKeyCompare,
|
||||
dictSdsNOPDestructor,
|
||||
dictObjectDestructor,
|
||||
};
|
||||
|
||||
/* g_pserver->lua_scripts sha (as sds string) -> scripts (as robj) cache. */
|
||||
dictType shaScriptObjectDictType = {
|
||||
dictSdsCaseHash, /* hash function */
|
||||
@@ -1426,10 +1463,7 @@ int htNeedsResize(dict *dict) {
|
||||
/* If the percentage of used slots in the HT reaches HASHTABLE_MIN_FILL
|
||||
* we resize the hash table to save memory */
|
||||
void tryResizeHashTables(int dbid) {
|
||||
if (htNeedsResize(g_pserver->db[dbid].pdict))
|
||||
dictResize(g_pserver->db[dbid].pdict);
|
||||
if (htNeedsResize(g_pserver->db[dbid].expires))
|
||||
dictResize(g_pserver->db[dbid].expires);
|
||||
g_pserver->db[dbid].tryResize();
|
||||
}
|
||||
|
||||
/* Our hash table implementation performs rehashing incrementally while
|
||||
@@ -1439,15 +1473,10 @@ void tryResizeHashTables(int dbid) {
|
||||
*
|
||||
* The function returns 1 if some rehashing was performed, otherwise 0
|
||||
* is returned. */
|
||||
int incrementallyRehash(int dbid) {
|
||||
int redisDbPersistentData::incrementallyRehash() {
|
||||
/* Keys dictionary */
|
||||
if (dictIsRehashing(g_pserver->db[dbid].pdict)) {
|
||||
dictRehashMilliseconds(g_pserver->db[dbid].pdict,1);
|
||||
return 1; /* already used our millisecond for this loop... */
|
||||
}
|
||||
/* Expires */
|
||||
if (dictIsRehashing(g_pserver->db[dbid].expires)) {
|
||||
dictRehashMilliseconds(g_pserver->db[dbid].expires,1);
|
||||
if (dictIsRehashing(m_pdict)) {
|
||||
dictRehashMilliseconds(m_pdict,1);
|
||||
return 1; /* already used our millisecond for this loop... */
|
||||
}
|
||||
return 0;
|
||||
@@ -1460,7 +1489,7 @@ int incrementallyRehash(int dbid) {
|
||||
* for dict.c to resize the hash tables accordingly to the fact we have o not
|
||||
* running childs. */
|
||||
void updateDictResizePolicy(void) {
|
||||
if (g_pserver->rdb_child_pid == -1 && g_pserver->aof_child_pid == -1)
|
||||
if (!g_pserver->FRdbSaveInProgress() && g_pserver->aof_child_pid == -1)
|
||||
dictEnableResize();
|
||||
else
|
||||
dictDisableResize();
|
||||
@@ -1707,7 +1736,7 @@ void databasesCron(void) {
|
||||
/* Perform hash tables rehashing if needed, but only if there are no
|
||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||
* as will cause a lot of copy-on-write of memory pages. */
|
||||
if (g_pserver->rdb_child_pid == -1 && g_pserver->aof_child_pid == -1) {
|
||||
if (!g_pserver->FRdbSaveInProgress() && g_pserver->aof_child_pid == -1) {
|
||||
/* We use global counters so if we stop the computation at a given
|
||||
* DB we'll be able to start from the successive in the next
|
||||
* cron loop iteration. */
|
||||
@@ -1728,7 +1757,7 @@ void databasesCron(void) {
|
||||
/* Rehash */
|
||||
if (g_pserver->activerehashing) {
|
||||
for (j = 0; j < dbs_per_call; j++) {
|
||||
int work_done = incrementallyRehash(rehash_db);
|
||||
int work_done = g_pserver->db[rehash_db].incrementallyRehash();
|
||||
if (work_done) {
|
||||
/* If the function did some work, stop here, we'll do
|
||||
* more at the next cron loop. */
|
||||
@@ -1887,9 +1916,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
long long size, used, vkeys;
|
||||
|
||||
size = dictSlots(g_pserver->db[j].pdict);
|
||||
used = dictSize(g_pserver->db[j].pdict);
|
||||
vkeys = dictSize(g_pserver->db[j].expires);
|
||||
size = g_pserver->db[j].slots();
|
||||
used = g_pserver->db[j].size();
|
||||
vkeys = g_pserver->db[j].expireSize();
|
||||
if (used || vkeys) {
|
||||
serverLog(LL_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);
|
||||
/* dictPrintStats(g_pserver->dict); */
|
||||
@@ -1916,20 +1945,37 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
/* Start a scheduled AOF rewrite if this was requested by the user while
|
||||
* a BGSAVE was in progress. */
|
||||
if (g_pserver->rdb_child_pid == -1 && g_pserver->aof_child_pid == -1 &&
|
||||
if (!g_pserver->FRdbSaveInProgress() && g_pserver->aof_child_pid == -1 &&
|
||||
g_pserver->aof_rewrite_scheduled)
|
||||
{
|
||||
rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
|
||||
/* Check if a background saving or AOF rewrite in progress terminated. */
|
||||
if (g_pserver->rdb_child_pid != -1 || g_pserver->aof_child_pid != -1 ||
|
||||
if (g_pserver->FRdbSaveInProgress() || g_pserver->aof_child_pid != -1 ||
|
||||
ldbPendingChildren())
|
||||
{
|
||||
int statloc;
|
||||
pid_t pid;
|
||||
|
||||
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
|
||||
if (g_pserver->FRdbSaveInProgress())
|
||||
{
|
||||
void *rval = nullptr;
|
||||
if (pthread_tryjoin_np(g_pserver->rdbThreadVars.rdb_child_thread, &rval))
|
||||
{
|
||||
if (errno != EBUSY && errno != EAGAIN)
|
||||
serverLog(LL_WARNING, "Error joining the background RDB save thread: %s\n", strerror(errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
int exitcode = (int)reinterpret_cast<ptrdiff_t>(rval);
|
||||
backgroundSaveDoneHandler(exitcode,0);
|
||||
if (exitcode == 0) receiveChildInfo();
|
||||
updateDictResizePolicy();
|
||||
closeChildInfoPipe();
|
||||
}
|
||||
}
|
||||
else if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = 0;
|
||||
|
||||
@@ -1937,13 +1983,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
if (pid == -1) {
|
||||
serverLog(LL_WARNING,"wait3() returned an error: %s. "
|
||||
"rdb_child_pid = %d, aof_child_pid = %d",
|
||||
"aof_child_pid = %d",
|
||||
strerror(errno),
|
||||
(int) g_pserver->rdb_child_pid,
|
||||
(int) g_pserver->aof_child_pid);
|
||||
} else if (pid == g_pserver->rdb_child_pid) {
|
||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||
} else if (pid == g_pserver->aof_child_pid) {
|
||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||
@@ -1984,7 +2026,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (g_pserver->aof_state == AOF_ON &&
|
||||
g_pserver->rdb_child_pid == -1 &&
|
||||
!g_pserver->FRdbSaveInProgress() &&
|
||||
g_pserver->aof_child_pid == -1 &&
|
||||
g_pserver->aof_rewrite_perc &&
|
||||
g_pserver->aof_current_size > g_pserver->aof_rewrite_min_size)
|
||||
@@ -2040,7 +2082,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
* Note: this code must be after the replicationCron() call above so
|
||||
* make sure when refactoring this file to keep this order. This is useful
|
||||
* because we want to give priority to RDB savings for replication. */
|
||||
if (g_pserver->rdb_child_pid == -1 && g_pserver->aof_child_pid == -1 &&
|
||||
if (!g_pserver->FRdbSaveInProgress() && g_pserver->aof_child_pid == -1 &&
|
||||
g_pserver->rdb_bgsave_scheduled &&
|
||||
(g_pserver->unixtime-g_pserver->lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
|
||||
g_pserver->lastbgsave_status == C_OK))
|
||||
@@ -2104,7 +2146,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
argv[0] = createStringObject("REPLCONF",8);
|
||||
argv[1] = createStringObject("GETACK",6);
|
||||
argv[2] = createStringObject("*",1); /* Not used argument. */
|
||||
replicationFeedSlaves(g_pserver->slaves, g_pserver->slaveseldb, argv, 3);
|
||||
replicationFeedSlaves(g_pserver->slaves, g_pserver->replicaseldb, argv, 3);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[2]);
|
||||
@@ -2509,7 +2551,7 @@ void initServerConfig(void) {
|
||||
|
||||
/* By default we want scripts to be always replicated by effects
|
||||
* (single commands executed by the script), and not by sending the
|
||||
* script to the slave / AOF. This is the new way starting from
|
||||
* script to the replica / AOF. This is the new way starting from
|
||||
* Redis 5. However it is possible to revert it via redis.conf. */
|
||||
g_pserver->lua_always_replicate_commands = 1;
|
||||
|
||||
@@ -2702,7 +2744,7 @@ void checkTcpBacklogSettings(void) {
|
||||
* impossible to bind, or no bind addresses were specified in the server
|
||||
* configuration but the function is not able to bind * for at least
|
||||
* one of the IPv4 or IPv6 protocols. */
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort) {
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListen) {
|
||||
int j;
|
||||
|
||||
/* Force binding of 0.0.0.0 if no bind address is specified, always
|
||||
@@ -2714,7 +2756,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
|
||||
/* Bind * for both IPv6 and IPv4, we enter here only if
|
||||
* g_pserver->bindaddr_count == 0. */
|
||||
fds[*count] = anetTcp6Server(serverTL->neterr,port,NULL,
|
||||
g_pserver->tcp_backlog, fReusePort);
|
||||
g_pserver->tcp_backlog, fReusePort, fFirstListen);
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
@@ -2726,7 +2768,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
|
||||
if (*count == 1 || unsupported) {
|
||||
/* Bind the IPv4 address as well. */
|
||||
fds[*count] = anetTcpServer(serverTL->neterr,port,NULL,
|
||||
g_pserver->tcp_backlog, fReusePort);
|
||||
g_pserver->tcp_backlog, fReusePort, fFirstListen);
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
@@ -2742,11 +2784,11 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
|
||||
} else if (strchr(g_pserver->bindaddr[j],':')) {
|
||||
/* Bind IPv6 address. */
|
||||
fds[*count] = anetTcp6Server(serverTL->neterr,port,g_pserver->bindaddr[j],
|
||||
g_pserver->tcp_backlog, fReusePort);
|
||||
g_pserver->tcp_backlog, fReusePort, fFirstListen);
|
||||
} else {
|
||||
/* Bind IPv4 address. */
|
||||
fds[*count] = anetTcpServer(serverTL->neterr,port,g_pserver->bindaddr[j],
|
||||
g_pserver->tcp_backlog, fReusePort);
|
||||
g_pserver->tcp_backlog, fReusePort, fFirstListen);
|
||||
}
|
||||
if (fds[*count] == ANET_ERR) {
|
||||
serverLog(LL_WARNING,
|
||||
@@ -2808,7 +2850,7 @@ static void initNetworkingThread(int iel, int fReusePort)
|
||||
if (fReusePort || (iel == IDX_EVENT_LOOP_MAIN))
|
||||
{
|
||||
if (g_pserver->port != 0 &&
|
||||
listenToPort(g_pserver->port,g_pserver->rgthreadvar[iel].ipfd,&g_pserver->rgthreadvar[iel].ipfd_count, fReusePort) == C_ERR)
|
||||
listenToPort(g_pserver->port,g_pserver->rgthreadvar[iel].ipfd,&g_pserver->rgthreadvar[iel].ipfd_count, fReusePort, (iel == IDX_EVENT_LOOP_MAIN)) == C_ERR)
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
@@ -2925,14 +2967,8 @@ void initServer(void) {
|
||||
|
||||
/* Create the Redis databases, and initialize other internal state. */
|
||||
for (int j = 0; j < cserver.dbnum; j++) {
|
||||
g_pserver->db[j].pdict = dictCreate(&dbDictType,NULL);
|
||||
g_pserver->db[j].expires = dictCreate(&keyptrDictType,NULL);
|
||||
g_pserver->db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
|
||||
g_pserver->db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
|
||||
g_pserver->db[j].watched_keys = dictCreate(&keylistDictType,NULL);
|
||||
g_pserver->db[j].id = j;
|
||||
g_pserver->db[j].avg_ttl = 0;
|
||||
g_pserver->db[j].defrag_later = listCreate();
|
||||
new (&g_pserver->db[j]) redisDb;
|
||||
g_pserver->db[j].initialize(j);
|
||||
}
|
||||
|
||||
/* Fixup Master Client Database */
|
||||
@@ -2956,7 +2992,7 @@ void initServer(void) {
|
||||
cserver.pid = getpid();
|
||||
g_pserver->clients_index = raxNew();
|
||||
g_pserver->clients_to_close = listCreate();
|
||||
g_pserver->slaveseldb = -1; /* Force to emit the first SELECT command. */
|
||||
g_pserver->replicaseldb = -1; /* Force to emit the first SELECT command. */
|
||||
g_pserver->ready_keys = listCreate();
|
||||
g_pserver->clients_waiting_acks = listCreate();
|
||||
g_pserver->get_ack_from_slaves = 0;
|
||||
@@ -2972,7 +3008,7 @@ void initServer(void) {
|
||||
listSetFreeMethod(g_pserver->pubsub_patterns,freePubsubPattern);
|
||||
listSetMatchMethod(g_pserver->pubsub_patterns,listMatchPubsubPattern);
|
||||
g_pserver->cronloops = 0;
|
||||
g_pserver->rdb_child_pid = -1;
|
||||
g_pserver->rdbThreadVars.fRdbThreadActive = false;
|
||||
g_pserver->aof_child_pid = -1;
|
||||
g_pserver->rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||
g_pserver->rdb_bgsave_scheduled = 0;
|
||||
@@ -3376,6 +3412,7 @@ void call(client *c, int flags) {
|
||||
latencyAddSampleIfNeeded(latency_event,duration/1000);
|
||||
slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration);
|
||||
}
|
||||
|
||||
if (flags & CMD_CALL_STATS) {
|
||||
/* use the real command that was executed (cmd and lastamc) may be
|
||||
* different, in case of MULTI-EXEC or re-written commands such as
|
||||
@@ -3449,6 +3486,16 @@ void call(client *c, int flags) {
|
||||
ProcessPendingAsyncWrites();
|
||||
|
||||
g_pserver->also_propagate = prev_also_propagate;
|
||||
|
||||
/* If the client has keys tracking enabled for client side caching,
|
||||
* make sure to remember the keys it fetched via this command. */
|
||||
if (c->cmd->flags & CMD_READONLY) {
|
||||
client *caller = (c->flags & CLIENT_LUA && g_pserver->lua_caller) ?
|
||||
g_pserver->lua_caller : c;
|
||||
if (caller->flags & CLIENT_TRACKING)
|
||||
trackingRememberKeys(caller);
|
||||
}
|
||||
|
||||
g_pserver->stat_numcommands++;
|
||||
}
|
||||
|
||||
@@ -3524,7 +3571,7 @@ int processCommand(client *c, int callFlags) {
|
||||
if (acl_retval == ACL_DENIED_CMD)
|
||||
addReplyErrorFormat(c,
|
||||
"-NOPERM this user has no permissions to run "
|
||||
"the '%s' command or its subcommnad", c->cmd->name);
|
||||
"the '%s' command or its subcommand", c->cmd->name);
|
||||
else
|
||||
addReplyErrorFormat(c,
|
||||
"-NOPERM this user has no permissions to access "
|
||||
@@ -3572,8 +3619,8 @@ int processCommand(client *c, int callFlags) {
|
||||
* propagation of DELs due to eviction. */
|
||||
if (g_pserver->maxmemory && !g_pserver->lua_timedout) {
|
||||
int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
/* freeMemoryIfNeeded may flush replica output buffers. This may result
|
||||
* into a replica, that may be the active client, to be freed. */
|
||||
if (serverTL->current_client == NULL) return C_ERR;
|
||||
|
||||
/* It was impossible to free enough memory, and the command the client
|
||||
@@ -3620,7 +3667,7 @@ int processCommand(client *c, int callFlags) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Don't accept write commands if this is a read only slave. But
|
||||
/* Don't accept write commands if this is a read only replica. But
|
||||
* accept write commands if this is our master. */
|
||||
if (listLength(g_pserver->masters) && g_pserver->repl_slave_ro &&
|
||||
!(c->flags & CLIENT_MASTER) &&
|
||||
@@ -3643,7 +3690,7 @@ int processCommand(client *c, int callFlags) {
|
||||
}
|
||||
|
||||
/* Only allow commands with flag "t", such as INFO, SLAVEOF and so on,
|
||||
* when slave-serve-stale-data is no and we are a slave with a broken
|
||||
* when replica-serve-stale-data is no and we are a replica with a broken
|
||||
* link with master. */
|
||||
if (FBrokenLinkToMaster() &&
|
||||
g_pserver->repl_serve_stale_data == 0 &&
|
||||
@@ -3686,7 +3733,12 @@ int processCommand(client *c, int callFlags) {
|
||||
queueMultiCommand(c);
|
||||
addReply(c,shared.queued);
|
||||
} else {
|
||||
std::unique_lock<decltype(c->db->lock)> ulock(c->db->lock);
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
g_pserver->db[idb].trackChanges();
|
||||
call(c,callFlags);
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
g_pserver->db[idb].processChanges();
|
||||
c->woff = g_pserver->master_repl_offset;
|
||||
if (listLength(g_pserver->ready_keys))
|
||||
handleClientsBlockedOnKeys();
|
||||
@@ -3727,7 +3779,7 @@ int prepareForShutdown(int flags) {
|
||||
/* Kill the saving child if there is a background saving in progress.
|
||||
We want to avoid race conditions, for instance our saving child may
|
||||
overwrite the synchronous saving did by SHUTDOWN. */
|
||||
if (g_pserver->rdb_child_pid != -1) {
|
||||
if (g_pserver->FRdbSaveInProgress()) {
|
||||
serverLog(LL_WARNING,"There is a child saving an .rdb. Killing it!");
|
||||
killRDBChild();
|
||||
}
|
||||
@@ -3758,7 +3810,7 @@ int prepareForShutdown(int flags) {
|
||||
/* Snapshotting. Perform a SYNC SAVE and exit */
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(rsiptr) != C_OK) {
|
||||
if (rdbSave(nullptr, rsiptr) != C_OK) {
|
||||
/* Ooops.. error saving! The best we can do is to continue
|
||||
* operating. Note that if there was a background saving process,
|
||||
* in the next cron() Redis will be notified that the background
|
||||
@@ -3775,7 +3827,7 @@ int prepareForShutdown(int flags) {
|
||||
unlink(cserver.pidfile);
|
||||
}
|
||||
|
||||
/* Best effort flush of slave output buffers, so that we hopefully
|
||||
/* Best effort flush of replica output buffers, so that we hopefully
|
||||
* send them pending writes. */
|
||||
flushSlavesOutputBuffers();
|
||||
|
||||
@@ -4087,10 +4139,12 @@ sds genRedisInfoString(const char *section) {
|
||||
"connected_clients:%lu\r\n"
|
||||
"client_recent_max_input_buffer:%zu\r\n"
|
||||
"client_recent_max_output_buffer:%zu\r\n"
|
||||
"blocked_clients:%d\r\n",
|
||||
"blocked_clients:%d\r\n"
|
||||
"current_client_thread:%d\r\n",
|
||||
listLength(g_pserver->clients)-listLength(g_pserver->slaves),
|
||||
maxin, maxout,
|
||||
g_pserver->blocked_clients);
|
||||
g_pserver->blocked_clients,
|
||||
static_cast<int>(serverTL - g_pserver->rgthreadvar));
|
||||
for (int ithread = 0; ithread < cserver.cthreads; ++ithread)
|
||||
{
|
||||
info = sdscatprintf(info,
|
||||
@@ -4239,12 +4293,12 @@ sds genRedisInfoString(const char *section) {
|
||||
"aof_last_cow_size:%zu\r\n",
|
||||
g_pserver->loading,
|
||||
g_pserver->dirty,
|
||||
g_pserver->rdb_child_pid != -1,
|
||||
g_pserver->FRdbSaveInProgress(),
|
||||
(intmax_t)g_pserver->lastsave,
|
||||
(g_pserver->lastbgsave_status == C_OK) ? "ok" : "err",
|
||||
(intmax_t)g_pserver->rdb_save_time_last,
|
||||
(intmax_t)((g_pserver->rdb_child_pid == -1) ?
|
||||
-1 : time(NULL)-g_pserver->rdb_save_time_start),
|
||||
(intmax_t)(g_pserver->FRdbSaveInProgress() ?
|
||||
time(NULL)-g_pserver->rdb_save_time_start : -1),
|
||||
g_pserver->stat_rdb_cow_bytes,
|
||||
g_pserver->aof_state != AOF_OFF,
|
||||
g_pserver->aof_child_pid != -1,
|
||||
@@ -4451,18 +4505,18 @@ sds genRedisInfoString(const char *section) {
|
||||
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = (client*)listNodeValue(ln);
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
const char *state = NULL;
|
||||
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
|
||||
char ip[NET_IP_STR_LEN], *slaveip = replica->slave_ip;
|
||||
int port;
|
||||
long lag = 0;
|
||||
|
||||
if (slaveip[0] == '\0') {
|
||||
if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1)
|
||||
if (anetPeerToString(replica->fd,ip,sizeof(ip),&port) == -1)
|
||||
continue;
|
||||
slaveip = ip;
|
||||
}
|
||||
switch(slave->replstate) {
|
||||
switch(replica->replstate) {
|
||||
case SLAVE_STATE_WAIT_BGSAVE_START:
|
||||
case SLAVE_STATE_WAIT_BGSAVE_END:
|
||||
state = "wait_bgsave";
|
||||
@@ -4475,14 +4529,14 @@ sds genRedisInfoString(const char *section) {
|
||||
break;
|
||||
}
|
||||
if (state == NULL) continue;
|
||||
if (slave->replstate == SLAVE_STATE_ONLINE)
|
||||
lag = time(NULL) - slave->repl_ack_time;
|
||||
if (replica->replstate == SLAVE_STATE_ONLINE)
|
||||
lag = time(NULL) - replica->repl_ack_time;
|
||||
|
||||
info = sdscatprintf(info,
|
||||
"slave%d:ip=%s,port=%d,state=%s,"
|
||||
"offset=%lld,lag=%ld\r\n",
|
||||
slaveid,slaveip,slave->slave_listening_port,state,
|
||||
(slave->repl_ack_off + slave->reploff_skipped), lag);
|
||||
slaveid,slaveip,replica->slave_listening_port,state,
|
||||
(replica->repl_ack_off + replica->reploff_skipped), lag);
|
||||
slaveid++;
|
||||
}
|
||||
}
|
||||
@@ -4560,12 +4614,19 @@ sds genRedisInfoString(const char *section) {
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
long long keys, vkeys;
|
||||
|
||||
keys = dictSize(g_pserver->db[j].pdict);
|
||||
vkeys = dictSize(g_pserver->db[j].expires);
|
||||
keys = g_pserver->db[j].size();
|
||||
vkeys = g_pserver->db[j].expireSize();
|
||||
|
||||
// Adjust TTL by the current time
|
||||
g_pserver->db[j].avg_ttl -= (g_pserver->mstime - g_pserver->db[j].last_expire_set);
|
||||
if (g_pserver->db[j].avg_ttl < 0)
|
||||
g_pserver->db[j].avg_ttl = 0;
|
||||
g_pserver->db[j].last_expire_set = g_pserver->mstime;
|
||||
|
||||
if (keys || vkeys) {
|
||||
info = sdscatprintf(info,
|
||||
"db%d:keys=%lld,expires=%lld,avg_ttl=%lld\r\n",
|
||||
j, keys, vkeys, g_pserver->db[j].avg_ttl);
|
||||
j, keys, vkeys, static_cast<long long>(g_pserver->db[j].avg_ttl));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4583,7 +4644,7 @@ void infoCommand(client *c) {
|
||||
}
|
||||
|
||||
void monitorCommand(client *c) {
|
||||
/* ignore MONITOR if already slave or in monitor mode */
|
||||
/* ignore MONITOR if already replica or in monitor mode */
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
if (c->flags & CLIENT_SLAVE) return;
|
||||
|
||||
@@ -4733,7 +4794,7 @@ static void sigShutdownHandler(int sig) {
|
||||
* on disk. */
|
||||
if (g_pserver->shutdown_asap && sig == SIGINT) {
|
||||
serverLogFromHandler(LL_WARNING, "You insist... exiting now.");
|
||||
rdbRemoveTempFile(getpid());
|
||||
rdbRemoveTempFile(g_pserver->rdbThreadVars.tmpfileNum);
|
||||
exit(1); /* Exit with an error since this was not a clean shutdown. */
|
||||
} else if (g_pserver->loading) {
|
||||
serverLogFromHandler(LL_WARNING, "Received shutdown signal during loading, exiting now.");
|
||||
@@ -4810,7 +4871,7 @@ void loadDataFromDisk(void) {
|
||||
while ((ln = listNext(&li)))
|
||||
{
|
||||
redisMaster *mi = (redisMaster*)listNodeValue(ln);
|
||||
/* If we are a slave, create a cached master from this
|
||||
/* If we are a replica, create a cached master from this
|
||||
* information, in order to allow partial resynchronizations
|
||||
* with masters. */
|
||||
replicationCacheMasterUsingMyself(mi);
|
||||
@@ -4830,6 +4891,12 @@ void redisOutOfMemoryHandler(size_t allocation_size) {
|
||||
serverPanic("Redis aborting for OUT OF MEMORY");
|
||||
}
|
||||
|
||||
void fuzzOutOfMemoryHandler(size_t allocation_size) {
|
||||
serverLog(LL_WARNING,"Out Of Memory allocating %zu bytes!",
|
||||
allocation_size);
|
||||
exit(EXIT_FAILURE); // don't crash because it causes false positives
|
||||
}
|
||||
|
||||
void redisSetProcTitle(const char *title) {
|
||||
#ifdef USE_SETPROCTITLE
|
||||
const char *server_mode = "";
|
||||
@@ -4952,7 +5019,7 @@ void incrementMvccTstamp()
|
||||
{
|
||||
uint64_t msPrev;
|
||||
__atomic_load(&g_pserver->mvcc_tstamp, &msPrev, __ATOMIC_ACQUIRE);
|
||||
msPrev >>= 20; // convert to milliseconds
|
||||
msPrev >>= MVCC_MS_SHIFT; // convert to milliseconds
|
||||
|
||||
long long mst;
|
||||
__atomic_load(&g_pserver->mstime, &mst, __ATOMIC_RELAXED);
|
||||
@@ -4962,7 +5029,7 @@ void incrementMvccTstamp()
|
||||
}
|
||||
else
|
||||
{
|
||||
atomicSet(g_pserver->mvcc_tstamp, ((uint64_t)g_pserver->mstime) << 20);
|
||||
atomicSet(g_pserver->mvcc_tstamp, ((uint64_t)g_pserver->mstime) << MVCC_MS_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5184,6 +5251,23 @@ int main(int argc, char **argv) {
|
||||
#endif
|
||||
moduleLoadFromQueue();
|
||||
ACLLoadUsersAtStartup();
|
||||
|
||||
// special case of FUZZING load from stdin then quit
|
||||
if (argc > 1 && strstr(argv[1],"rdbfuzz-mode") != NULL)
|
||||
{
|
||||
zmalloc_set_oom_handler(fuzzOutOfMemoryHandler);
|
||||
#ifdef __AFL_HAVE_MANUAL_CONTROL
|
||||
__AFL_INIT();
|
||||
#endif
|
||||
rio rdb;
|
||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||
startLoading(stdin);
|
||||
rioInitWithFile(&rdb,stdin);
|
||||
rdbLoadRio(&rdb,&rsi,0);
|
||||
stopLoading();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
loadDataFromDisk();
|
||||
if (g_pserver->cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == C_ERR) {
|
||||
@@ -5220,7 +5304,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
aeReleaseLock(); //Finally we can dump the lock
|
||||
moduleReleaseGIL(true);
|
||||
|
||||
|
||||
serverAssert(cserver.cthreads > 0 && cserver.cthreads <= MAX_EVENT_LOOPS);
|
||||
pthread_t rgthread[MAX_EVENT_LOOPS];
|
||||
for (int iel = 0; iel < cserver.cthreads; ++iel)
|
||||
|
||||
+550
-42
@@ -42,6 +42,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <cmath>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
@@ -53,6 +54,8 @@
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
@@ -81,12 +84,14 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
N-elements flat arrays */
|
||||
#include "rax.h" /* Radix tree */
|
||||
#include "uuid.h"
|
||||
#include "semiorderedset.h"
|
||||
|
||||
/* Following includes allow test functions to be called from Redis main() */
|
||||
#include "zipmap.h"
|
||||
#include "sha1.h"
|
||||
#include "endianconv.h"
|
||||
#include "crc64.h"
|
||||
#include "IStorage.h"
|
||||
|
||||
extern int g_fTestMode;
|
||||
|
||||
@@ -127,6 +132,11 @@ public:
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const redisObject& operator*() const
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return !m_ptr;
|
||||
@@ -243,7 +253,7 @@ public:
|
||||
#define CONFIG_DEFAULT_ACTIVE_REPLICA 0
|
||||
#define CONFIG_DEFAULT_ENABLE_MULTIMASTER 0
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 64 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* CPU max % for keys collection */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
||||
@@ -327,9 +337,9 @@ public:
|
||||
#define AOF_WAIT_REWRITE 2 /* AOF waits rewrite to start appending */
|
||||
|
||||
/* Client flags */
|
||||
#define CLIENT_SLAVE (1<<0) /* This client is a slave server */
|
||||
#define CLIENT_MASTER (1<<1) /* This client is a master server */
|
||||
#define CLIENT_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */
|
||||
#define CLIENT_SLAVE (1<<0) /* This client is a repliaca */
|
||||
#define CLIENT_MASTER (1<<1) /* This client is a master */
|
||||
#define CLIENT_MONITOR (1<<2) /* This client is a replica monitor, see MONITOR */
|
||||
#define CLIENT_MULTI (1<<3) /* This client is in a MULTI context */
|
||||
#define CLIENT_BLOCKED (1<<4) /* The client is waiting in a blocking operation */
|
||||
#define CLIENT_DIRTY_CAS (1<<5) /* Watched keys modified. EXEC will fail. */
|
||||
@@ -359,7 +369,17 @@ public:
|
||||
#define CLIENT_LUA_DEBUG_SYNC (1<<26) /* EVAL debugging without fork() */
|
||||
#define CLIENT_MODULE (1<<27) /* Non connected client used by some module. */
|
||||
#define CLIENT_PROTECTED (1<<28) /* Client should not be freed for now. */
|
||||
#define CLIENT_FORCE_REPLY (1<<29) /* Should addReply be forced to write the text? */
|
||||
#define CLIENT_PENDING_READ (1<<29) /* The client has pending reads and was put
|
||||
in the list of clients we can read
|
||||
from. */
|
||||
#define CLIENT_PENDING_COMMAND (1<<30) /* Used in threaded I/O to signal after
|
||||
we return single threaded that the
|
||||
client has already pending commands
|
||||
to be executed. */
|
||||
#define CLIENT_TRACKING (1<<31) /* Client enabled keys tracking in order to
|
||||
perform client side caching. */
|
||||
#define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */
|
||||
#define CLIENT_FORCE_REPLY (1ULL<<33) /* Should addReply be forced to write the text? */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@@ -383,7 +403,7 @@ public:
|
||||
#define CLIENT_TYPE_MASTER 3 /* Master. */
|
||||
#define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output
|
||||
buffer configuration. Just the first
|
||||
three: normal, slave, pubsub. */
|
||||
three: normal, replica, pubsub. */
|
||||
|
||||
/* Slave replication state. Used in g_pserver->repl_state for slaves to remember
|
||||
* what to do next. */
|
||||
@@ -409,12 +429,12 @@ public:
|
||||
#define REPL_STATE_CONNECTED 17 /* Connected to master */
|
||||
|
||||
/* State of slaves from the POV of the master. Used in client->replstate.
|
||||
* In SEND_BULK and ONLINE state the slave receives new updates
|
||||
* In SEND_BULK and ONLINE state the replica receives new updates
|
||||
* in its output queue. In the WAIT_BGSAVE states instead the server is waiting
|
||||
* to start the next background saving in order to send updates to it. */
|
||||
#define SLAVE_STATE_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
|
||||
#define SLAVE_STATE_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
|
||||
#define SLAVE_STATE_SEND_BULK 8 /* Sending RDB file to slave. */
|
||||
#define SLAVE_STATE_SEND_BULK 8 /* Sending RDB file to replica. */
|
||||
#define SLAVE_STATE_ONLINE 9 /* RDB file transmitted, sending just updates. */
|
||||
|
||||
/* Slave capabilities. */
|
||||
@@ -422,7 +442,7 @@ public:
|
||||
#define SLAVE_CAPA_EOF (1<<0) /* Can parse the RDB EOF streaming format. */
|
||||
#define SLAVE_CAPA_PSYNC2 (1<<1) /* Supports PSYNC2 protocol. */
|
||||
|
||||
/* Synchronous read timeout - slave side */
|
||||
/* Synchronous read timeout - replica side */
|
||||
#define CONFIG_REPL_SYNCIO_TIMEOUT 5
|
||||
|
||||
/* List related stuff */
|
||||
@@ -531,7 +551,7 @@ public:
|
||||
/* RDB active child save type. */
|
||||
#define RDB_CHILD_TYPE_NONE 0
|
||||
#define RDB_CHILD_TYPE_DISK 1 /* RDB is written to disk. */
|
||||
#define RDB_CHILD_TYPE_SOCKET 2 /* RDB is written to slave socket. */
|
||||
#define RDB_CHILD_TYPE_SOCKET 2 /* RDB is written to replica socket. */
|
||||
|
||||
/* Keyspace changes notification classes. Every class is associated with a
|
||||
* character for configuration purposes. */
|
||||
@@ -707,20 +727,32 @@ typedef struct RedisModuleDigest {
|
||||
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
|
||||
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
|
||||
|
||||
#define OBJ_SHARED_REFCOUNT INT_MAX
|
||||
#define OBJ_SHARED_REFCOUNT (0x7FFFFFFF)
|
||||
#define OBJ_MVCC_INVALID (0xFFFFFFFFFFFFFFFFULL)
|
||||
|
||||
#define MVCC_MS_SHIFT 20
|
||||
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned encoding:4;
|
||||
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
|
||||
* LFU data (least significant 8 bits frequency
|
||||
* and most significant 16 bits access time). */
|
||||
mutable std::atomic<int> refcount;
|
||||
private:
|
||||
mutable std::atomic<unsigned> refcount {0};
|
||||
public:
|
||||
uint64_t mvcc_tstamp;
|
||||
void *m_ptr;
|
||||
} robj;
|
||||
|
||||
inline bool FExpires() const { return refcount.load(std::memory_order_relaxed) >> 31; }
|
||||
void SetFExpires(bool fExpires);
|
||||
|
||||
void setrefcount(unsigned ref);
|
||||
unsigned getrefcount(std::memory_order order) const { return (refcount.load(order) & ~(1U << 31)); }
|
||||
void addref() const { refcount.fetch_add(1, std::memory_order_acq_rel); }
|
||||
unsigned release() const { return refcount.fetch_sub(1, std::memory_order_acq_rel) & ~(1U << 31); }
|
||||
} robj;
|
||||
static_assert(sizeof(redisObject) == 24, "object size is critical, don't increase");
|
||||
|
||||
__attribute__((always_inline)) inline const void *ptrFromObj(robj_roptr &o)
|
||||
{
|
||||
@@ -746,12 +778,276 @@ __attribute__((always_inline)) inline char *szFromObj(const robj *o)
|
||||
return (char*)ptrFromObj(o);
|
||||
}
|
||||
|
||||
class expireEntryFat
|
||||
{
|
||||
friend class expireEntry;
|
||||
public:
|
||||
struct subexpireEntry
|
||||
{
|
||||
long long when;
|
||||
std::unique_ptr<const char, void(*)(const char*)> spsubkey;
|
||||
|
||||
subexpireEntry(long long when, const char *subkey)
|
||||
: when(when), spsubkey(subkey, sdsfree)
|
||||
{}
|
||||
|
||||
subexpireEntry(const subexpireEntry &e)
|
||||
: when(e.when), spsubkey(nullptr, sdsfree)
|
||||
{
|
||||
if (e.spsubkey)
|
||||
spsubkey = std::unique_ptr<const char, void(*)(const char*)>((const char*)sdsdup((sds)e.spsubkey.get()), sdsfree);
|
||||
}
|
||||
|
||||
subexpireEntry(subexpireEntry &&e) = default;
|
||||
subexpireEntry& operator=(subexpireEntry &&e) = default;
|
||||
|
||||
bool operator<(long long when) const noexcept { return this->when < when; }
|
||||
bool operator<(const subexpireEntry &se) { return this->when < se.when; }
|
||||
};
|
||||
|
||||
private:
|
||||
sds m_keyPrimary;
|
||||
std::vector<subexpireEntry> m_vecexpireEntries; // Note a NULL for the sds portion means the expire is for the primary key
|
||||
|
||||
public:
|
||||
expireEntryFat(sds keyPrimary)
|
||||
: m_keyPrimary(keyPrimary)
|
||||
{}
|
||||
|
||||
expireEntryFat(const expireEntryFat &e) = default;
|
||||
expireEntryFat(expireEntryFat &&e) = default;
|
||||
|
||||
long long when() const noexcept { return m_vecexpireEntries.front().when; }
|
||||
const char *key() const noexcept { return m_keyPrimary; }
|
||||
|
||||
bool operator<(long long when) const noexcept { return this->when() < when; }
|
||||
|
||||
void expireSubKey(const char *szSubkey, long long when)
|
||||
{
|
||||
// First check if the subkey already has an expiration
|
||||
for (auto &entry : m_vecexpireEntries)
|
||||
{
|
||||
if (szSubkey != nullptr)
|
||||
{
|
||||
// if this is a subkey expiry then its not a match if the expireEntry is either for the
|
||||
// primary key or a different subkey
|
||||
if (entry.spsubkey == nullptr || sdscmp((sds)entry.spsubkey.get(), (sds)szSubkey) != 0)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entry.spsubkey != nullptr)
|
||||
continue;
|
||||
}
|
||||
m_vecexpireEntries.erase(m_vecexpireEntries.begin() + (&entry - m_vecexpireEntries.data()));
|
||||
break;
|
||||
}
|
||||
auto itrInsert = std::lower_bound(m_vecexpireEntries.begin(), m_vecexpireEntries.end(), when);
|
||||
const char *subkey = (szSubkey) ? sdsdup(szSubkey) : nullptr;
|
||||
m_vecexpireEntries.emplace(itrInsert, when, subkey);
|
||||
}
|
||||
|
||||
bool FEmpty() const noexcept { return m_vecexpireEntries.empty(); }
|
||||
const subexpireEntry &nextExpireEntry() const noexcept { return m_vecexpireEntries.front(); }
|
||||
void popfrontExpireEntry() { m_vecexpireEntries.erase(m_vecexpireEntries.begin()); }
|
||||
const subexpireEntry &operator[](size_t idx) { return m_vecexpireEntries[idx]; }
|
||||
size_t size() const noexcept { return m_vecexpireEntries.size(); }
|
||||
};
|
||||
|
||||
class expireEntry {
|
||||
union
|
||||
{
|
||||
sds m_key;
|
||||
expireEntryFat *m_pfatentry;
|
||||
} u;
|
||||
long long m_when; // LLONG_MIN means this is a fat entry and we should use the pointer
|
||||
|
||||
public:
|
||||
class iter
|
||||
{
|
||||
friend class expireEntry;
|
||||
expireEntry *m_pentry = nullptr;
|
||||
size_t m_idx = 0;
|
||||
|
||||
public:
|
||||
iter(expireEntry *pentry, size_t idx)
|
||||
: m_pentry(pentry), m_idx(idx)
|
||||
{}
|
||||
|
||||
iter &operator++() { ++m_idx; return *this; }
|
||||
|
||||
const char *subkey() const
|
||||
{
|
||||
if (m_pentry->FFat())
|
||||
return (*m_pentry->pfatentry())[m_idx].spsubkey.get();
|
||||
return nullptr;
|
||||
}
|
||||
long long when() const
|
||||
{
|
||||
if (m_pentry->FFat())
|
||||
return (*m_pentry->pfatentry())[m_idx].when;
|
||||
return m_pentry->when();
|
||||
}
|
||||
|
||||
bool operator!=(const iter &other)
|
||||
{
|
||||
return m_idx != other.m_idx;
|
||||
}
|
||||
|
||||
const iter &operator*() const { return *this; }
|
||||
};
|
||||
|
||||
expireEntry(sds key, const char *subkey, long long when)
|
||||
{
|
||||
if (subkey != nullptr)
|
||||
{
|
||||
m_when = LLONG_MIN;
|
||||
u.m_pfatentry = new (MALLOC_LOCAL) expireEntryFat(key);
|
||||
u.m_pfatentry->expireSubKey(subkey, when);
|
||||
}
|
||||
else
|
||||
{
|
||||
u.m_key = key;
|
||||
m_when = when;
|
||||
}
|
||||
}
|
||||
|
||||
expireEntry(expireEntryFat *pfatentry)
|
||||
{
|
||||
u.m_pfatentry = pfatentry;
|
||||
m_when = LLONG_MIN;
|
||||
}
|
||||
|
||||
expireEntry(const expireEntry &e)
|
||||
{
|
||||
u.m_key = e.u.m_key;
|
||||
m_when = e.m_when;
|
||||
if (e.FFat())
|
||||
u.m_pfatentry = new (MALLOC_LOCAL) expireEntryFat(*e.u.m_pfatentry);
|
||||
}
|
||||
|
||||
expireEntry(expireEntry &&e)
|
||||
{
|
||||
u.m_key = e.u.m_key;
|
||||
m_when = e.m_when;
|
||||
e.u.m_key = (char*)key(); // we do this so it can still be found in the set
|
||||
e.m_when = 0;
|
||||
}
|
||||
|
||||
~expireEntry()
|
||||
{
|
||||
if (FFat())
|
||||
delete u.m_pfatentry;
|
||||
}
|
||||
|
||||
void setKeyUnsafe(sds key)
|
||||
{
|
||||
if (FFat())
|
||||
u.m_pfatentry->m_keyPrimary = key;
|
||||
else
|
||||
u.m_key = key;
|
||||
}
|
||||
|
||||
inline bool FFat() const noexcept { return m_when == LLONG_MIN; }
|
||||
expireEntryFat *pfatentry() { assert(FFat()); return u.m_pfatentry; }
|
||||
|
||||
|
||||
bool operator==(const char *key) const noexcept
|
||||
{
|
||||
return this->key() == key;
|
||||
}
|
||||
|
||||
bool operator<(const expireEntry &e) const noexcept
|
||||
{
|
||||
return when() < e.when();
|
||||
}
|
||||
bool operator<(long long when) const noexcept
|
||||
{
|
||||
return this->when() < when;
|
||||
}
|
||||
|
||||
const char *key() const noexcept
|
||||
{
|
||||
if (FFat())
|
||||
return u.m_pfatentry->key();
|
||||
return u.m_key;
|
||||
}
|
||||
long long when() const noexcept
|
||||
{
|
||||
if (FFat())
|
||||
return u.m_pfatentry->when();
|
||||
return m_when;
|
||||
}
|
||||
|
||||
void update(const char *subkey, long long when)
|
||||
{
|
||||
if (!FFat())
|
||||
{
|
||||
if (subkey == nullptr)
|
||||
{
|
||||
m_when = when;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have to upgrade to a fat entry
|
||||
long long whenT = m_when;
|
||||
sds keyPrimary = u.m_key;
|
||||
m_when = LLONG_MIN;
|
||||
u.m_pfatentry = new (MALLOC_LOCAL) expireEntryFat(keyPrimary);
|
||||
u.m_pfatentry->expireSubKey(nullptr, whenT);
|
||||
// at this point we're fat so fall through
|
||||
}
|
||||
}
|
||||
u.m_pfatentry->expireSubKey(subkey, when);
|
||||
}
|
||||
|
||||
iter begin() { return iter(this, 0); }
|
||||
iter end()
|
||||
{
|
||||
if (FFat())
|
||||
return iter(this, u.m_pfatentry->size());
|
||||
return iter(this, 1);
|
||||
}
|
||||
|
||||
void erase(iter &itr)
|
||||
{
|
||||
if (!FFat())
|
||||
throw -1; // assert
|
||||
pfatentry()->m_vecexpireEntries.erase(
|
||||
pfatentry()->m_vecexpireEntries.begin() + itr.m_idx);
|
||||
}
|
||||
|
||||
bool FGetPrimaryExpire(long long *pwhen)
|
||||
{
|
||||
*pwhen = -1;
|
||||
for (auto itr : *this)
|
||||
{
|
||||
if (itr.subkey() == nullptr)
|
||||
{
|
||||
*pwhen = itr.when();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
explicit operator const char*() const noexcept { return key(); }
|
||||
explicit operator long long() const noexcept { return when(); }
|
||||
};
|
||||
typedef semiorderedset<expireEntry, const char *, true /*expireEntry can be memmoved*/> expireset;
|
||||
|
||||
/* The a string name for an object's type as listed above
|
||||
* Native types are checked against the OBJ_STRING, OBJ_LIST, OBJ_* defines,
|
||||
* and Module types have their registered name returned. */
|
||||
const char *getObjectTypeName(robj_roptr o);
|
||||
|
||||
/* Macro used to initialize a Redis object allocated on the stack.
|
||||
* Note that this macro is taken near the structure definition to make sure
|
||||
* we'll update it when the structure is changed, to avoid bugs like
|
||||
* bug #85 introduced exactly in this way. */
|
||||
#define initStaticStringObject(_var,_ptr) do { \
|
||||
_var.refcount = 1; \
|
||||
_var.setrefcount(1); \
|
||||
_var.type = OBJ_STRING; \
|
||||
_var.encoding = OBJ_ENCODING_RAW; \
|
||||
_var.m_ptr = _ptr; \
|
||||
@@ -773,18 +1069,192 @@ typedef struct clientReplyBlock {
|
||||
#endif
|
||||
} clientReplyBlock;
|
||||
|
||||
struct dictEntry;
|
||||
class dict_const_iter
|
||||
{
|
||||
friend struct redisDb;
|
||||
friend class redisDbPersistentData;
|
||||
protected:
|
||||
dictEntry *de;
|
||||
public:
|
||||
explicit dict_const_iter(dictEntry *de)
|
||||
: de(de)
|
||||
{}
|
||||
|
||||
const char *key() const { return de ? (const char*)dictGetKey(de) : nullptr; }
|
||||
robj_roptr val() const { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
const robj* operator->() const { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
operator robj_roptr() const { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
|
||||
bool operator==(std::nullptr_t) const { return de == nullptr; }
|
||||
bool operator!=(std::nullptr_t) const { return de != nullptr; }
|
||||
bool operator==(const dict_const_iter &other) { return de == other.de; }
|
||||
};
|
||||
class dict_iter : public dict_const_iter
|
||||
{
|
||||
public:
|
||||
explicit dict_iter(dictEntry *de)
|
||||
: dict_const_iter(de)
|
||||
{}
|
||||
sds key() { return de ? (sds)dictGetKey(de) : nullptr; }
|
||||
robj *val() { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
robj *operator->() { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
operator robj*() const { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
};
|
||||
|
||||
class redisDbPersistentData
|
||||
{
|
||||
friend void dictDbKeyDestructor(void *privdata, void *key);
|
||||
public:
|
||||
~redisDbPersistentData();
|
||||
|
||||
static void swap(redisDbPersistentData *db1, redisDbPersistentData *db2);
|
||||
|
||||
size_t slots() const { return dictSlots(m_pdict); }
|
||||
size_t size() const { return dictSize(m_pdict); }
|
||||
void expand(uint64_t slots) { dictExpand(m_pdict, slots); }
|
||||
|
||||
void trackkey(robj_roptr o)
|
||||
{
|
||||
trackkey(szFromObj(o));
|
||||
}
|
||||
|
||||
void trackkey(const char *key)
|
||||
{
|
||||
if (m_fTrackingChanges && !m_fAllChanged)
|
||||
m_setchanged.insert(std::string(key, sdslen(key)));
|
||||
}
|
||||
|
||||
dict_iter find(const char *key)
|
||||
{
|
||||
dictEntry *de = dictFind(m_pdict, key);
|
||||
ensure(de);
|
||||
return dict_iter(de);
|
||||
}
|
||||
|
||||
dict_iter find(robj_roptr key)
|
||||
{
|
||||
return find(szFromObj(key));
|
||||
}
|
||||
|
||||
dict_iter random()
|
||||
{
|
||||
dictEntry *de = dictGetRandomKey(m_pdict);
|
||||
ensure(de);
|
||||
return dict_iter(de);
|
||||
}
|
||||
|
||||
const expireEntry &random_expire()
|
||||
{
|
||||
return m_setexpire->random_value();
|
||||
}
|
||||
|
||||
auto findExpire(const char *key)
|
||||
{
|
||||
return m_setexpire->find(key);
|
||||
}
|
||||
|
||||
dict_iter end() { return dict_iter(nullptr); }
|
||||
dict_const_iter end() const { return dict_const_iter(nullptr); }
|
||||
|
||||
void getStats(char *buf, size_t bufsize) { dictGetStats(buf, bufsize, m_pdict); }
|
||||
void getExpireStats(char *buf, size_t bufsize) { m_setexpire->getstats(buf, bufsize); }
|
||||
|
||||
bool insert(char *k, robj *o);
|
||||
void tryResize();
|
||||
int incrementallyRehash();
|
||||
void updateValue(dict_iter itr, robj *val);
|
||||
bool syncDelete(robj *key);
|
||||
bool asyncDelete(robj *key);
|
||||
size_t expireSize() const { return m_setexpire->size(); }
|
||||
int removeExpire(robj *key, dict_iter itr);
|
||||
int removeSubkeyExpire(robj *key, robj *subkey);
|
||||
void clear(void(callback)(void*));
|
||||
void emptyDbAsync();
|
||||
bool iterate(std::function<bool(const char*, robj*)> fn);
|
||||
void setExpire(robj *key, robj *subkey, long long when);
|
||||
void setExpire(expireEntry &&e);
|
||||
expireEntry *getExpire(robj_roptr key);
|
||||
void initialize();
|
||||
|
||||
void trackChanges() { m_fTrackingChanges++; }
|
||||
void processChanges();
|
||||
|
||||
// This should only be used if you look at the key, we do not fixup
|
||||
// objects stored elsewhere
|
||||
dict *dictUnsafeKeyOnly() { return m_pdict; }
|
||||
|
||||
expireset *setexpireUnsafe() { return m_setexpire; }
|
||||
const expireset *setexpire() { return m_setexpire; }
|
||||
|
||||
std::shared_ptr<redisDbPersistentData> createSnapshot();
|
||||
void endSnapshot(const redisDbPersistentData *psnapshot);
|
||||
|
||||
private:
|
||||
void ensure(const char *key);
|
||||
void ensure(dictEntry *de);
|
||||
void storeDatabase();
|
||||
void storeKey(const char *key, size_t cchKey, robj *o);
|
||||
|
||||
// Keyspace
|
||||
dict *m_pdict = nullptr; /* The keyspace for this DB */
|
||||
int m_fTrackingChanges = 0; // Note: Stack based
|
||||
bool m_fAllChanged = false;
|
||||
std::set<std::string> m_setchanged;
|
||||
IStorage *m_pstorage = nullptr;
|
||||
|
||||
// Expire
|
||||
expireset *m_setexpire = nullptr;
|
||||
|
||||
std::shared_ptr<redisDbPersistentData> m_spdbSnapshot;
|
||||
};
|
||||
|
||||
/* Redis database representation. There are multiple databases identified
|
||||
* by integers from 0 (the default database) up to the max configured
|
||||
* database. The database number is the 'id' field in the structure. */
|
||||
typedef struct redisDb {
|
||||
dict *pdict; /* The keyspace for this DB */
|
||||
dict *expires; /* Timeout of keys with a timeout set */
|
||||
typedef struct redisDb : public redisDbPersistentData
|
||||
{
|
||||
// Legacy C API, Do not add more
|
||||
friend void tryResizeHashTables(int);
|
||||
friend int dbSyncDelete(redisDb *db, robj *key);
|
||||
friend int dbAsyncDelete(redisDb *db, robj *key);
|
||||
friend long long emptyDb(int dbnum, int flags, void(callback)(void*));
|
||||
friend void scanGenericCommand(struct client *c, robj_roptr o, unsigned long cursor);
|
||||
friend int dbSwapDatabases(int id1, int id2);
|
||||
friend int removeExpire(redisDb *db, robj *key);
|
||||
friend void setExpire(struct client *c, redisDb *db, robj *key, robj *subkey, long long when);
|
||||
friend void setExpire(client *c, redisDb *db, robj *key, expireEntry &&e);
|
||||
friend void evictionPoolPopulate(int dbid, redisDb *db, expireset *setexpire, struct evictionPoolEntry *pool);
|
||||
friend void activeDefragCycle(void);
|
||||
friend int freeMemoryIfNeeded(void);
|
||||
friend void activeExpireCycle(int);
|
||||
friend void expireSlaveKeys(void);
|
||||
|
||||
typedef ::dict_const_iter const_iter;
|
||||
typedef ::dict_iter iter;
|
||||
|
||||
redisDb()
|
||||
: expireitr(nullptr)
|
||||
{}
|
||||
void initialize(int id);
|
||||
|
||||
void dbOverwriteCore(redisDb::iter itr, robj *key, robj *val, bool fUpdateMvcc, bool fRemoveExpire);
|
||||
|
||||
|
||||
bool FKeyExpires(const char *key);
|
||||
size_t clear(bool fAsync, void(callback)(void*));
|
||||
|
||||
public:
|
||||
expireset::setiter expireitr;
|
||||
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
|
||||
dict *ready_keys; /* Blocked keys that received a PUSH */
|
||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||
int id; /* Database ID */
|
||||
long long avg_ttl; /* Average TTL, just for stats */
|
||||
long long last_expire_set; /* when the last expire was set */
|
||||
double avg_ttl; /* Average TTL, just for stats */
|
||||
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
|
||||
|
||||
fastlock lock;
|
||||
} redisDb;
|
||||
|
||||
/* Client MULTI/EXEC state */
|
||||
@@ -924,11 +1394,12 @@ typedef struct client {
|
||||
time_t ctime; /* Client creation time. */
|
||||
time_t lastinteraction; /* Time of the last interaction, used for timeout */
|
||||
time_t obuf_soft_limit_reached_time;
|
||||
std::atomic<int> flags; /* Client flags: CLIENT_* macros. */
|
||||
std::atomic<uint64_t> flags; /* Client flags: CLIENT_* macros. */
|
||||
int casyncOpsPending;
|
||||
int fPendingAsyncWrite; /* NOTE: Not a flag because it is written to outside of the client lock (locked by the global lock instead) */
|
||||
int authenticated; /* Needed when the default user requires auth. */
|
||||
int replstate; /* Replication state if this is a slave. */
|
||||
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
|
||||
int replstate; /* Replication state if this is a replica. */
|
||||
int repl_put_online_on_ack; /* Install replica write handler on ACK. */
|
||||
int repldbfd; /* Replication DB file descriptor. */
|
||||
off_t repldboff; /* Replication DB file offset. */
|
||||
off_t repldbsize; /* Replication DB file size. */
|
||||
@@ -936,10 +1407,10 @@ typedef struct client {
|
||||
long long read_reploff; /* Read replication offset if this is a master. */
|
||||
long long reploff; /* Applied replication offset if this is a master. */
|
||||
long long reploff_skipped; /* Repl backlog we did not send to this client */
|
||||
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
|
||||
long long repl_ack_time;/* Replication ack time, if this is a slave. */
|
||||
long long repl_ack_off; /* Replication ack offset, if this is a replica. */
|
||||
long long repl_ack_time;/* Replication ack time, if this is a replica. */
|
||||
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
|
||||
copying this slave output buffer
|
||||
copying this replica output buffer
|
||||
should use. */
|
||||
char replid[CONFIG_RUN_ID_SIZE+1]; /* Master replication ID (if master). */
|
||||
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
|
||||
@@ -960,6 +1431,11 @@ typedef struct client {
|
||||
/* UUIDs are transient and lost when the server is shut down */
|
||||
unsigned char uuid[UUID_BINARY_LEN];
|
||||
|
||||
/* If this client is in tracking mode and this field is non zero,
|
||||
* invalidation messages for keys fetched by this client will be send to
|
||||
* the specified client ID. */
|
||||
uint64_t client_tracking_redirection;
|
||||
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
char buf[PROTO_REPLY_CHUNK_BYTES];
|
||||
@@ -1112,6 +1588,9 @@ typedef struct rdbSaveInfo {
|
||||
char repl_id[CONFIG_RUN_ID_SIZE+1]; /* Replication ID. */
|
||||
long long repl_offset; /* Replication offset. */
|
||||
int fForceSetKey;
|
||||
|
||||
/* Used In Save */
|
||||
long long master_repl_offset;
|
||||
} rdbSaveInfo;
|
||||
|
||||
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1, TRUE}
|
||||
@@ -1175,7 +1654,7 @@ struct redisMaster {
|
||||
char master_replid[CONFIG_RUN_ID_SIZE+1]; /* Master PSYNC runid. */
|
||||
long long master_initial_offset; /* Master PSYNC offset. */
|
||||
|
||||
int repl_state; /* Replication status if the instance is a slave */
|
||||
int repl_state; /* Replication status if the instance is a replica */
|
||||
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
|
||||
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
|
||||
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
|
||||
@@ -1369,7 +1848,13 @@ struct redisServer {
|
||||
/* RDB persistence */
|
||||
long long dirty; /* Changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||
pid_t rdb_child_pid; /* PID of RDB saving child */
|
||||
struct _rdbThreadVars
|
||||
{
|
||||
bool fRdbThreadActive = false;
|
||||
volatile bool fRdbThreadCancel = false;
|
||||
pthread_t rdb_child_thread; /* PID of RDB saving child */
|
||||
int tmpfileNum = 0;
|
||||
} rdbThreadVars;
|
||||
struct saveparam *saveparams; /* Save points array for RDB */
|
||||
int saveparamslen; /* Number of saving points */
|
||||
char *rdb_filename; /* Name of RDB file */
|
||||
@@ -1385,7 +1870,7 @@ struct redisServer {
|
||||
int lastbgsave_status; /* C_OK or C_ERR */
|
||||
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
|
||||
int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */
|
||||
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */
|
||||
int rdb_pipe_read_result_from_child; /* of each replica in diskless SYNC. */
|
||||
/* Pipe and data structures for child -> parent info sharing. */
|
||||
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
|
||||
struct {
|
||||
@@ -1405,8 +1890,8 @@ struct redisServer {
|
||||
char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/
|
||||
long long master_repl_offset; /* My current replication offset */
|
||||
long long second_replid_offset; /* Accept offsets up to this for replid2. */
|
||||
int slaveseldb; /* Last SELECTed DB in replication output */
|
||||
int repl_ping_slave_period; /* Master pings the slave every N seconds */
|
||||
int replicaseldb; /* Last SELECTed DB in replication output */
|
||||
int repl_ping_slave_period; /* Master pings the replica every N seconds */
|
||||
char *repl_backlog; /* Replication backlog for partial syncs */
|
||||
long long repl_backlog_size; /* Backlog circular buffer size */
|
||||
long long repl_backlog_histlen; /* Backlog actual data length */
|
||||
@@ -1423,7 +1908,7 @@ struct redisServer {
|
||||
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */
|
||||
int repl_diskless_sync; /* Send RDB to slaves sockets directly. */
|
||||
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
|
||||
/* Replication (slave) */
|
||||
/* Replication (replica) */
|
||||
list *masters;
|
||||
int enable_multimaster;
|
||||
int repl_timeout; /* Timeout after N seconds of master idle */
|
||||
@@ -1455,6 +1940,8 @@ struct redisServer {
|
||||
unsigned int blocked_clients; /* # of clients executing a blocking cmd.*/
|
||||
unsigned int blocked_clients_by_type[BLOCKED_NUM];
|
||||
list *ready_keys; /* List of readyList structures for BLPOP & co */
|
||||
/* Client side caching. */
|
||||
unsigned int tracking_clients; /* # of clients with tracking enabled.*/
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
@@ -1492,7 +1979,7 @@ struct redisServer {
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
int cluster_require_full_coverage; /* If true, put the cluster down if
|
||||
there is at least an uncovered slot.*/
|
||||
int cluster_slave_no_failover; /* Prevent slave from starting a failover
|
||||
int cluster_slave_no_failover; /* Prevent replica from starting a failover
|
||||
if the master is in failure state. */
|
||||
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
|
||||
int cluster_announce_port; /* base port to announce on cluster bus. */
|
||||
@@ -1546,6 +2033,8 @@ struct redisServer {
|
||||
|
||||
/* System hardware info */
|
||||
size_t system_memory_size; /* Total memory in system as reported by OS */
|
||||
|
||||
bool FRdbSaveInProgress() const { return rdbThreadVars.fRdbThreadActive; }
|
||||
};
|
||||
|
||||
typedef struct pubsubPattern {
|
||||
@@ -1651,9 +2140,11 @@ extern dictType zsetDictType;
|
||||
extern dictType clusterNodesDictType;
|
||||
extern dictType clusterNodesBlackListDictType;
|
||||
extern dictType dbDictType;
|
||||
extern dictType dbSnapshotDictType;
|
||||
extern dictType shaScriptObjectDictType;
|
||||
extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
extern dictType hashDictType;
|
||||
extern dictType keylistDictType;
|
||||
extern dictType replScriptCacheDictType;
|
||||
extern dictType keyptrDictType;
|
||||
extern dictType modulesDictType;
|
||||
@@ -1694,7 +2185,7 @@ void redisSetProcTitle(const char *title);
|
||||
/* networking.c -- Networking and Client related operations */
|
||||
client *createClient(int fd, int iel);
|
||||
void closeTimedoutClients(void);
|
||||
void freeClient(client *c);
|
||||
bool freeClient(client *c);
|
||||
void freeClientAsync(client *c);
|
||||
void resetClient(client *c);
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
@@ -1761,7 +2252,7 @@ const char *getClientTypeName(int cclass);
|
||||
void flushSlavesOutputBuffers(void);
|
||||
void disconnectSlaves(void);
|
||||
void disconnectSlavesExcept(unsigned char *uuid);
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort);
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListen);
|
||||
void pauseClients(mstime_t duration);
|
||||
int clientsArePaused(void);
|
||||
int processEventsWhileBlocked(int iel);
|
||||
@@ -1791,6 +2282,7 @@ void addReplyPushLenAsync(client *c, long length);
|
||||
void addReplyLongLongAsync(client *c, long long ll);
|
||||
|
||||
void ProcessPendingAsyncWrites(void);
|
||||
client *lookupClientByID(uint64_t id);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(client *c, const char *fmt, ...)
|
||||
@@ -1802,6 +2294,12 @@ void addReplyErrorFormat(client *c, const char *fmt, ...);
|
||||
void addReplyStatusFormat(client *c, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
/* Client side caching (tracking mode) */
|
||||
void enableTracking(client *c, uint64_t redirect_to);
|
||||
void disableTracking(client *c);
|
||||
void trackingRememberKeys(client *c);
|
||||
void trackingInvalidateKey(robj *keyobj);
|
||||
|
||||
/* List data type */
|
||||
void listTypeTryConversion(robj *subject, robj *value);
|
||||
void listTypePush(robj *subject, robj *value, int where);
|
||||
@@ -1875,8 +2373,12 @@ const char *strEncoding(int encoding);
|
||||
int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
unsigned long long estimateObjectIdleTime(robj_roptr o);
|
||||
void trimStringObjectIfNeeded(robj *o);
|
||||
|
||||
robj *deserializeStoredObject(const void *data, size_t cb);
|
||||
sds serializeStoredObject(robj_roptr o);
|
||||
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
@@ -1908,9 +2410,10 @@ void replicationSendNewlineToMaster(struct redisMaster *mi);
|
||||
long long replicationGetSlaveOffset(struct redisMaster *mi);
|
||||
char *replicationGetSlaveName(client *c);
|
||||
long long getPsyncInitialOffset(void);
|
||||
int replicationSetupSlaveForFullResync(client *slave, long long offset);
|
||||
int replicationSetupSlaveForFullResync(client *replica, long long offset);
|
||||
void changeReplicationId(void);
|
||||
void clearReplicationId2(void);
|
||||
void mergeReplicationId(const char *);
|
||||
void chopReplicationBacklog(void);
|
||||
void replicationCacheMasterUsingMyself(struct redisMaster *mi);
|
||||
void feedReplicationBacklog(const void *ptr, size_t len);
|
||||
@@ -1928,7 +2431,7 @@ int writeCommandsDeniedByDiskError(void);
|
||||
|
||||
/* RDB persistence */
|
||||
#include "rdb.h"
|
||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
|
||||
int rdbSaveRio(rio *rdb, redisDbPersistentData **rgpdb, int *error, int flags, rdbSaveInfo *rsi);
|
||||
void killRDBChild(void);
|
||||
|
||||
/* AOF persistence */
|
||||
@@ -2129,6 +2632,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify);
|
||||
void freePubsubPattern(const void *p);
|
||||
int listMatchPubsubPattern(void *a, void *b);
|
||||
int pubsubPublishMessage(robj *channel, robj *message);
|
||||
void addReplyPubsubMessage(client *c, robj *channel, robj *msg);
|
||||
|
||||
/* Keyspace events notification */
|
||||
void notifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
@@ -2145,17 +2649,18 @@ int rewriteConfig(char *path);
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
int removeExpire(redisDb *db, robj *key);
|
||||
int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey);
|
||||
void propagateExpire(redisDb *db, robj *key, int lazy);
|
||||
int expireIfNeeded(redisDb *db, robj *key);
|
||||
long long getExpire(redisDb *db, robj_roptr key);
|
||||
void setExpire(client *c, redisDb *db, robj *key, long long when);
|
||||
void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when);
|
||||
void setExpire(client *c, redisDb *db, robj *key, expireEntry &&entry);
|
||||
robj_roptr lookupKeyRead(redisDb *db, robj *key);
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key);
|
||||
robj_roptr lookupKeyReadOrReply(client *c, robj *key, robj *reply);
|
||||
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
||||
robj_roptr lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj *objectCommandLookup(client *c, robj *key);
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
robj_roptr objectCommandLookup(client *c, robj *key);
|
||||
robj_roptr objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock);
|
||||
#define LOOKUP_NONE 0
|
||||
@@ -2188,7 +2693,6 @@ void slotToKeyAdd(robj *key);
|
||||
void slotToKeyDel(robj *key);
|
||||
void slotToKeyFlush(void);
|
||||
int dbAsyncDelete(redisDb *db, robj *key);
|
||||
void emptyDbAsync(redisDb *db);
|
||||
void slotToKeyFlushAsync(void);
|
||||
size_t lazyfreeGetPendingObjectsCount(void);
|
||||
void freeObjAsync(robj *o);
|
||||
@@ -2212,6 +2716,7 @@ void clusterPropagatePublish(robj *channel, robj *message);
|
||||
void migrateCloseTimedoutSockets(void);
|
||||
void clusterBeforeSleep(void);
|
||||
int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, unsigned char *payload, uint32_t len);
|
||||
void createDumpPayload(rio *payload, robj_roptr o, robj *key);
|
||||
|
||||
/* Sentinel */
|
||||
void initSentinelConfig(void);
|
||||
@@ -2343,6 +2848,8 @@ void mgetCommand(client *c);
|
||||
void monitorCommand(client *c);
|
||||
void expireCommand(client *c);
|
||||
void expireatCommand(client *c);
|
||||
void expireMemberCommand(client *c);
|
||||
void expireMemberAtCommand(client *c);
|
||||
void pexpireCommand(client *c);
|
||||
void pexpireatCommand(client *c);
|
||||
void getsetCommand(client *c);
|
||||
@@ -2503,9 +3010,10 @@ void xorDigest(unsigned char *digest, const void *ptr, size_t len);
|
||||
int populateCommandTableParseFlags(struct redisCommand *c, const char *strflags);
|
||||
|
||||
int moduleGILAcquiredByModule(void);
|
||||
extern bool g_fInCrash;
|
||||
static inline int GlobalLocksAcquired(void) // Used in asserts to verify all global locks are correctly acquired for a server-thread to operate
|
||||
{
|
||||
return aeThreadOwnsLock() || moduleGILAcquiredByModule();
|
||||
return aeThreadOwnsLock() || moduleGILAcquiredByModule() || g_fInCrash;
|
||||
}
|
||||
|
||||
inline int ielFromEventLoop(const aeEventLoop *eventLoop)
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long dur
|
||||
(unsigned long)
|
||||
sdslen(szFromObj(argv[j])) - SLOWLOG_ENTRY_MAX_STRING);
|
||||
se->argv[j] = createObject(OBJ_STRING,s);
|
||||
} else if (argv[j]->refcount == OBJ_SHARED_REFCOUNT) {
|
||||
} else if (argv[j]->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT) {
|
||||
se->argv[j] = argv[j];
|
||||
} else {
|
||||
/* Here we need to dupliacate the string objects composing the
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
/* ----------------- Blocking sockets I/O with timeouts --------------------- */
|
||||
|
||||
/* Redis performs most of the I/O in a nonblocking way, with the exception
|
||||
* of the SYNC command where the slave does it in a blocking way, and
|
||||
* of the SYNC command where the replica does it in a blocking way, and
|
||||
* the MIGRATE command that must be blocking in order to be atomic from the
|
||||
* point of view of the two instances (one migrating the key and one receiving
|
||||
* the key). This is why need the following blocking I/O functions.
|
||||
|
||||
+2
-2
@@ -219,9 +219,9 @@ int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) {
|
||||
return setobj->encoding;
|
||||
}
|
||||
|
||||
int setTypeRandomElement(robj_roptr setobj, const char **sdsele, const int64_t *llele)
|
||||
int setTypeRandomElement(robj_roptr setobj, const char **sdsele, int64_t *llele)
|
||||
{
|
||||
return setTypeRandomElement(setobj.unsafe_robjcast(), (sds*)sdsele, const_cast<int64_t*>(llele));
|
||||
return setTypeRandomElement(setobj.unsafe_robjcast(), (sds*)sdsele, llele);
|
||||
}
|
||||
|
||||
unsigned long setTypeSize(robj_roptr subject) {
|
||||
|
||||
+1
-1
@@ -803,7 +803,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
|
||||
* RETRYCOUNT <count> FORCE JUSTID LASTID <id>.
|
||||
*
|
||||
* Note that JUSTID is useful in order to avoid that XCLAIM will do
|
||||
* useless work in the slave side, trying to fetch the stream item. */
|
||||
* useless work in the replica side, trying to fetch the stream item. */
|
||||
robj *argv[14];
|
||||
argv[0] = createStringObject("XCLAIM",6);
|
||||
argv[1] = key;
|
||||
|
||||
+2
-2
@@ -85,7 +85,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
}
|
||||
setKey(c->db,key,val);
|
||||
g_pserver->dirty++;
|
||||
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
|
||||
if (expire) setExpire(c,c->db,key,nullptr,mstime()+milliseconds);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
|
||||
if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"expire",key,c->db->id);
|
||||
@@ -353,7 +353,7 @@ void incrDecrCommand(client *c, long long incr) {
|
||||
}
|
||||
value += incr;
|
||||
|
||||
if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
|
||||
if (o && o->getrefcount(std::memory_order_relaxed) == 1 && o->encoding == OBJ_ENCODING_INT &&
|
||||
(value < 0 || value >= OBJ_SHARED_INTEGERS) &&
|
||||
value >= LONG_MIN && value <= LONG_MAX)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/* tracking.c - Client side caching: keys tracking and invalidation
|
||||
*
|
||||
* Copyright (c) 2019, Salvatore Sanfilippo <antirez 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 "server.h"
|
||||
|
||||
/* The tracking table is constituted by 2^24 radix trees (each tree, and the
|
||||
* table itself, are allocated in a lazy way only when needed) tracking
|
||||
* clients that may have certain keys in their local, client side, cache.
|
||||
*
|
||||
* Keys are grouped into 2^24 slots, in a way similar to Redis Cluster hash
|
||||
* slots, however here the function we use is crc64, taking the least
|
||||
* significant 24 bits of the output.
|
||||
*
|
||||
* When a client enables tracking with "CLIENT TRACKING on", each key served to
|
||||
* the client is hashed to one of such slots, and Redis will remember what
|
||||
* client may have keys about such slot. Later, when a key in a given slot is
|
||||
* modified, all the clients that may have local copies of keys in that slot
|
||||
* will receive an invalidation message. There is no distinction of database
|
||||
* number: a single table is used.
|
||||
*
|
||||
* Clients will normally take frequently requested objects in memory, removing
|
||||
* them when invalidation messages are received. A strategy clients may use is
|
||||
* to just cache objects in a dictionary, associating to each cached object
|
||||
* some incremental epoch, or just a timestamp. When invalidation messages are
|
||||
* received clients may store, in a different table, the timestamp (or epoch)
|
||||
* of the invalidation of such given slot: later when accessing objects, the
|
||||
* eviction of stale objects may be performed in a lazy way by checking if the
|
||||
* cached object timestamp is older than the invalidation timestamp for such
|
||||
* objects.
|
||||
*
|
||||
* The output of the 24 bit hash function is very large (more than 16 million
|
||||
* possible slots), so clients that may want to use less resources may only
|
||||
* use the most significant bits instead of the full 24 bits. */
|
||||
#define TRACKING_TABLE_SIZE (1<<24)
|
||||
rax **TrackingTable = NULL;
|
||||
robj *TrackingChannelName;
|
||||
|
||||
/* Remove the tracking state from the client 'c'. Note that there is not much
|
||||
* to do for us here, if not to decrement the counter of the clients in
|
||||
* tracking mode, because we just store the ID of the client in the tracking
|
||||
* table, so we'll remove the ID reference in a lazy way. Otherwise when a
|
||||
* client with many entries in the table is removed, it would cost a lot of
|
||||
* time to do the cleanup. */
|
||||
void disableTracking(client *c) {
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
g_pserver->tracking_clients--;
|
||||
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR);
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable the tracking state for the client 'c', and as a side effect allocates
|
||||
* the tracking table if needed. If the 'redirect_to' argument is non zero, the
|
||||
* invalidation messages for this client will be sent to the client ID
|
||||
* specified by the 'redirect_to' argument. Note that if such client will
|
||||
* eventually get freed, we'll send a message to the original client to
|
||||
* inform it of the condition. Multiple clients can redirect the invalidation
|
||||
* messages to the same client ID. */
|
||||
void enableTracking(client *c, uint64_t redirect_to) {
|
||||
if (c->flags & CLIENT_TRACKING) return;
|
||||
c->flags |= CLIENT_TRACKING;
|
||||
c->flags &= ~CLIENT_TRACKING_BROKEN_REDIR;
|
||||
c->client_tracking_redirection = redirect_to;
|
||||
g_pserver->tracking_clients++;
|
||||
if (TrackingTable == NULL) {
|
||||
TrackingTable = (rax**)zcalloc(sizeof(rax*) * TRACKING_TABLE_SIZE, MALLOC_LOCAL);
|
||||
TrackingChannelName = createStringObject("__redis__:invalidate",20);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called after the excution of a readonly command in the
|
||||
* case the client 'c' has keys tracking enabled. It will populate the
|
||||
* tracking ivalidation table according to the keys the user fetched, so that
|
||||
* Redis will know what are the clients that should receive an invalidation
|
||||
* message with certain groups of keys are modified. */
|
||||
void trackingRememberKeys(client *c) {
|
||||
int numkeys;
|
||||
int *keys = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
|
||||
if (keys == NULL) return;
|
||||
|
||||
for(int j = 0; j < numkeys; j++) {
|
||||
int idx = keys[j];
|
||||
sds sdskey = (sds)ptrFromObj(c->argv[idx]);
|
||||
uint64_t hash = crc64(0,
|
||||
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
|
||||
if (TrackingTable[hash] == NULL)
|
||||
TrackingTable[hash] = raxNew();
|
||||
raxTryInsert(TrackingTable[hash],
|
||||
(unsigned char*)&c->id,sizeof(c->id),NULL,NULL);
|
||||
}
|
||||
getKeysFreeResult(keys);
|
||||
}
|
||||
|
||||
/* This function is called from signalModifiedKey() or other places in Redis
|
||||
* when a key changes value. In the context of keys tracking, our task here is
|
||||
* to send a notification to every client that may have keys about such . */
|
||||
void trackingInvalidateKey(robj *keyobj) {
|
||||
sds sdskey = (sds)ptrFromObj(keyobj);
|
||||
uint64_t hash = crc64(0,
|
||||
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
|
||||
if (TrackingTable == NULL || TrackingTable[hash] == NULL) return;
|
||||
|
||||
raxIterator ri;
|
||||
raxStart(&ri,TrackingTable[hash]);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
uint64_t id;
|
||||
memcpy(&id,ri.key,ri.key_len);
|
||||
client *c = lookupClientByID(id);
|
||||
int using_redirection = 0;
|
||||
if (c->client_tracking_redirection) {
|
||||
client *redir = lookupClientByID(c->client_tracking_redirection);
|
||||
if (!redir) {
|
||||
/* We need to signal to the original connection that we
|
||||
* are unable to send invalidation messages to the redirected
|
||||
* connection, because the client no longer exist. */
|
||||
if (c->resp > 2) {
|
||||
addReplyPushLen(c,3);
|
||||
addReplyBulkCBuffer(c,"tracking-redir-broken",21);
|
||||
addReplyLongLong(c,c->client_tracking_redirection);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
c = redir;
|
||||
using_redirection = 1;
|
||||
}
|
||||
|
||||
/* Only send such info for clients in RESP version 3 or more. However
|
||||
* if redirection is active, and the connection we redirect to is
|
||||
* in Pub/Sub mode, we can support the feature with RESP 2 as well,
|
||||
* by sending Pub/Sub messages in the __redis__:invalidate channel. */
|
||||
if (c->resp > 2) {
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCBuffer(c,"invalidate",10);
|
||||
addReplyLongLong(c,hash);
|
||||
} else if (using_redirection && c->flags & CLIENT_PUBSUB) {
|
||||
robj *msg = createStringObjectFromLongLong(hash);
|
||||
addReplyPubsubMessage(c,TrackingChannelName,msg);
|
||||
decrRefCount(msg);
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
/* Free the tracking table: we'll create the radix tree and populate it
|
||||
* again if more keys will be modified in this hash slot. */
|
||||
raxFree(TrackingTable[hash]);
|
||||
TrackingTable[hash] = NULL;
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#define KEYDB_REAL_VERSION "5.0.0"
|
||||
#define KEYDB_REAL_VERSION "0.0.0"
|
||||
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
|
||||
|
||||
@@ -93,5 +93,18 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
|
||||
assert_equal {0} [$master del testkey1]
|
||||
assert_equal {0} [$slave del testkey1]
|
||||
}
|
||||
|
||||
test {Active replica different databases} {
|
||||
$master select 3
|
||||
$master set testkey abcd
|
||||
$master select 2
|
||||
$master del testkey
|
||||
$slave select 3
|
||||
wait_for_condition 50 1000 {
|
||||
[string match abcd [$slave get testkey]]
|
||||
} else {
|
||||
fail "Replication failed to propogate DB 3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ set ::all_tests {
|
||||
unit/quit
|
||||
unit/aofrw
|
||||
unit/acl
|
||||
unit/rreplay
|
||||
integration/block-repl
|
||||
integration/replication
|
||||
integration/replication-2
|
||||
|
||||
@@ -214,6 +214,64 @@ start_server {tags {"bitops"}} {
|
||||
r bitop or x a b
|
||||
} {32}
|
||||
|
||||
test {BITOP lshift size} {
|
||||
r set a " "
|
||||
r bitop lshift x a 1
|
||||
} {2}
|
||||
|
||||
test {BITOP rshift size} {
|
||||
r set a " "
|
||||
r bitop rshift x a 1
|
||||
} {1}
|
||||
|
||||
test {BITOP rshift 0 byte} {
|
||||
r set a " "
|
||||
r bitop rshift x a 8
|
||||
} {0}
|
||||
|
||||
test {BITOP rshift underflow} {
|
||||
r set a " "
|
||||
r bitop rshift x a 65
|
||||
} {0}
|
||||
|
||||
test {BITOP lshift string} {
|
||||
r set a "abcdefg"
|
||||
r bitop lshift x a 8
|
||||
r get x
|
||||
} "\x00abcdefg"
|
||||
|
||||
test {BITOP lshift char} {
|
||||
r set a "\xAA"
|
||||
r bitop lshift x a 4
|
||||
r get x
|
||||
} "\xA0\x0A"
|
||||
|
||||
test {BITOP rshift char} {
|
||||
r set a "\xAA"
|
||||
r bitop rshift x a 3
|
||||
r get x
|
||||
} "\x15"
|
||||
|
||||
test {BITOP lshift carry} {
|
||||
r set a "\xFF"
|
||||
r bitop lshift x a 1
|
||||
r get x
|
||||
} "\xFE\x01"
|
||||
|
||||
test {BITOP rshift carry} {
|
||||
r set a "\x00\xFF"
|
||||
r bitop rshift x a 1
|
||||
r get x
|
||||
} "\x80\x7F"
|
||||
|
||||
test {BITOP rshift reciprocal} {
|
||||
r flushdb
|
||||
r set a "abcdefg"
|
||||
r bitop lshift b a 14
|
||||
r bitop rshift res b 14
|
||||
r get res
|
||||
} "abcdefg\x00"
|
||||
|
||||
test {BITPOS bit=0 with empty key returns 0} {
|
||||
r del str
|
||||
r bitpos str 0
|
||||
|
||||
@@ -15,7 +15,7 @@ start_server {tags {"obuf-limits"}} {
|
||||
if {![regexp {omem=([0-9]+)} $c - omem]} break
|
||||
if {$omem > 200000} break
|
||||
}
|
||||
assert {$omem >= 80000 && $omem < 200000}
|
||||
assert {$omem >= 70000 && $omem < 200000}
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
start_server {tags {"rreplay"}} {
|
||||
|
||||
test {RREPLAY use current db} {
|
||||
r debug force-master
|
||||
r select 4
|
||||
r set dbnum invalid
|
||||
r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$5\r\ndbnum\r\n\$4\r\nfour\r\n"
|
||||
r get dbnum
|
||||
} {four}
|
||||
reconnect
|
||||
|
||||
test {RREPLAY db different} {
|
||||
r debug force-master
|
||||
r select 4
|
||||
r set testkey four
|
||||
r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$7\r\ntestkey\r\n\$4\r\nbebe\r\n" 2
|
||||
r select 4
|
||||
assert { [r get testkey] == "four" }
|
||||
r select 2
|
||||
r get testkey
|
||||
} {bebe}
|
||||
|
||||
reconnect
|
||||
|
||||
test {RREPLAY not master} {
|
||||
assert_error "*master*" {r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$7\r\ntestkey\r\n\$4\r\nbebe\r\n" 2}
|
||||
}
|
||||
|
||||
r flushdb
|
||||
}
|
||||
@@ -53,6 +53,51 @@ start_server {tags {"scan"}} {
|
||||
assert_equal 100 [llength $keys]
|
||||
}
|
||||
|
||||
test "SCAN TYPE" {
|
||||
r flushdb
|
||||
# populate only creates strings
|
||||
r debug populate 1000
|
||||
|
||||
# Check non-strings are excluded
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r scan $cur type "list"]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
assert_equal 0 [llength $keys] "non-strings excluded"
|
||||
|
||||
# Check strings are included
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r scan $cur type "string"]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
assert_equal 1000 [llength $keys] "strings included"
|
||||
|
||||
# Check all three args work together
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r scan $cur type "string" match "key:*" count 10]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
assert_equal 1000 [llength $keys]
|
||||
}
|
||||
|
||||
foreach enc {intset hashtable} {
|
||||
test "SSCAN with encoding $enc" {
|
||||
# Create the Set
|
||||
|
||||
@@ -80,9 +80,11 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} {
|
||||
}
|
||||
|
||||
test {SLOWLOG - can be disabled} {
|
||||
r config set slowlog-max-len 1
|
||||
r config set slowlog-log-slower-than 1
|
||||
r slowlog reset
|
||||
assert_equal [r slowlog len] 1
|
||||
r debug sleep 0.2
|
||||
assert_equal [r slowlog len] 1
|
||||
r config set slowlog-log-slower-than -1
|
||||
r slowlog reset
|
||||
r debug sleep 0.2
|
||||
|
||||
@@ -202,7 +202,7 @@ REDIS_CHKCONFIG_INFO=\
|
||||
# chkconfig: - 58 74\n
|
||||
# description: redis_${REDIS_PORT} is the redis daemon.\n
|
||||
### BEGIN INIT INFO\n
|
||||
# Provides: redis_6379\n
|
||||
# Provides: redis_${REDIS_PORT}\n
|
||||
# Required-Start: \$network \$local_fs \$remote_fs\n
|
||||
# Required-Stop: \$network \$local_fs \$remote_fs\n
|
||||
# Default-Start: 2 3 4 5\n
|
||||
|
||||
Reference in new issue
Block a user