commit
19bb67b6d0
26
CONTRIBUTING
26
CONTRIBUTING
@ -8,13 +8,13 @@ each source file that you contribute.
|
||||
# IMPORTANT: HOW TO USE REDIS GITHUB ISSUES
|
||||
|
||||
* Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature
|
||||
requests. Everything else belongs to the Redis Google Group.
|
||||
requests. Everything else belongs to the Redis Google Group:
|
||||
|
||||
https://groups.google.com/forum/m/#!forum/Redis-db
|
||||
|
||||
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:
|
||||
|
||||
@ -22,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:
|
||||
@ -30,9 +35,16 @@ each source file that you contribute.
|
||||
a. Fork Redis on github ( http://help.github.com/fork-a-repo/ )
|
||||
b. Create a topic branch (git checkout -b my_branch)
|
||||
c. Push to your branch (git push origin my_branch)
|
||||
d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ )
|
||||
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!
|
||||
|
53
MANIFESTO
53
MANIFESTO
@ -34,7 +34,21 @@ Redis Manifesto
|
||||
so that the complexity is obvious and more complex operations can be
|
||||
performed as the sum of the basic operations.
|
||||
|
||||
4 - Code is like a poem; it's not just something we write to reach some
|
||||
4 - We believe in code efficiency. Computers get faster and faster, yet we
|
||||
believe that abusing computing capabilities is not wise: the amount of
|
||||
operations you can do for a given amount of energy remains anyway a
|
||||
significant parameter: it allows to do more with less computers and, at
|
||||
the same time, having a smaller environmental impact. Similarly Redis is
|
||||
able to "scale down" to smaller devices. It is perfectly usable in a
|
||||
Raspberry Pi and other small ARM based computers. Faster code having
|
||||
just the layers of abstractions that are really needed will also result,
|
||||
often, in more predictable performances. We think likewise about memory
|
||||
usage, one of the fundamental goals of the Redis project is to
|
||||
incrementally build more and more memory efficient data structures, so that
|
||||
problems that were not approachable in RAM in the past will be perfectly
|
||||
fine to handle in the future.
|
||||
|
||||
5 - Code is like a poem; it's not just something we write to reach some
|
||||
practical result. Sometimes people that are far from the Redis philosophy
|
||||
suggest using other code written by other authors (frequently in other
|
||||
languages) in order to implement something Redis currently lacks. But to us
|
||||
@ -45,23 +59,48 @@ Redis Manifesto
|
||||
when needed. At the same time, when writing the Redis story we're trying to
|
||||
write smaller stories that will fit in to other code.
|
||||
|
||||
5 - We're against complexity. We believe designing systems is a fight against
|
||||
6 - We're against complexity. We believe designing systems is a fight against
|
||||
complexity. We'll accept to fight the complexity when it's worthwhile but
|
||||
we'll try hard to recognize when a small feature is not worth 1000s of lines
|
||||
of code. Most of the time the best way to fight complexity is by not
|
||||
creating it at all.
|
||||
creating it at all. Complexity is also a form of lock-in: code that is
|
||||
very hard to understand cannot be modified by users in an independent way
|
||||
regardless of the license. One of the main Redis goals is to remain
|
||||
understandable, enough for a single programmer to have a clear idea of how
|
||||
it works in detail just reading the source code for a couple of weeks.
|
||||
|
||||
6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
7 - Threading is not a silver bullet. Instead of making Redis threaded we
|
||||
believe on the idea of an efficient (mostly) single threaded Redis core.
|
||||
Multiple of such cores, that may run in the same computer or may run
|
||||
in multiple computers, are abstracted away as a single big system by
|
||||
higher order protocols and features: Redis Cluster and the upcoming
|
||||
Redis Proxy are our main goals. A shared nothing approach is not just
|
||||
much simpler (see the previous point in this document), is also optimal
|
||||
in NUMA systems. In the specific case of Redis it allows for each instance
|
||||
to have a more limited amount of data, making the Redis persist-by-fork
|
||||
approach more sounding. In the future we may explore parallelism only for
|
||||
I/O, which is the low hanging fruit: minimal complexity could provide an
|
||||
improved single process experience.
|
||||
|
||||
8 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
naturally into a distributed version of Redis and 2) a more complex API that
|
||||
supports multi-key operations. Both are useful if used judiciously but
|
||||
there's no way to make the more complex multi-keys API distributed in an
|
||||
opaque way without violating our other principles. We don't want to provide
|
||||
the illusion of something that will work magically when actually it can't in
|
||||
all cases. Instead we'll provide commands to quickly migrate keys from one
|
||||
instance to another to perform multi-key operations and expose the tradeoffs
|
||||
to the user.
|
||||
instance to another to perform multi-key operations and expose the
|
||||
trade-offs to the user.
|
||||
|
||||
7 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
9 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
only way it can be worth is by enjoying it. When there is no longer joy in
|
||||
writing code, the best thing to do is stop. To prevent this, we'll avoid
|
||||
taking paths that will make Redis less of a joy to develop.
|
||||
|
||||
10 - All the above points are put together in what we call opportunistic
|
||||
programming: trying to get the most for the user with minimal increases
|
||||
in complexity (hanging fruits). Solve 95% of the problem with 5% of the
|
||||
code when it is acceptable. Avoid a fixed schedule but follow the flow of
|
||||
user requests, inspiration, Redis internal readiness for certain features
|
||||
(sometimes many past changes reach a critical point making a previously
|
||||
complex feature very easy to obtain).
|
||||
|
22
README.md
22
README.md
@ -119,7 +119,7 @@ parameter (the path of the configuration file):
|
||||
It is possible to alter the Redis configuration by passing parameters directly
|
||||
as options using the command line. Examples:
|
||||
|
||||
% ./redis-server --port 9999 --slaveof 127.0.0.1 6379
|
||||
% ./redis-server --port 9999 --replicaof 127.0.0.1 6379
|
||||
% ./redis-server /etc/redis/6379.conf --loglevel debug
|
||||
|
||||
All the options in redis.conf are also supported as options using the command
|
||||
@ -166,6 +166,8 @@ for Ubuntu and Debian systems:
|
||||
% cd utils
|
||||
% ./install_server.sh
|
||||
|
||||
_Note_: `install_server.sh` will not work on Mac OSX; it is built for Linux only.
|
||||
|
||||
The script will ask you a few questions and will setup everything you need
|
||||
to run Redis properly as a background daemon that will start again on
|
||||
system reboots.
|
||||
@ -216,7 +218,7 @@ Inside the root are the following important directories:
|
||||
|
||||
* `src`: contains the Redis implementation, written in C.
|
||||
* `tests`: contains the unit tests, implemented in Tcl.
|
||||
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `anitrez/redis`. An exception to this rule is `deps/geohash-int` which is the low level geocoding library used by Redis: it originated from a different project, but at this point it diverged so much that it is developed as a separated entity directly inside the Redis repository.
|
||||
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`.
|
||||
|
||||
There are a few more directories but they are not very important for our goals
|
||||
here. We'll focus mostly on `src`, where the Redis implementation is contained,
|
||||
@ -227,7 +229,7 @@ of complexity incrementally.
|
||||
Note: lately Redis was refactored quite a bit. Function names and file
|
||||
names have been changed, so you may find that this documentation reflects the
|
||||
`unstable` branch more closely. For instance in Redis 3.0 the `server.c`
|
||||
and `server.h` files were named to `redis.c` and `redis.h`. However the overall
|
||||
and `server.h` files were named `redis.c` and `redis.h`. However the overall
|
||||
structure is the same. Keep in mind that all the new developments and pull
|
||||
requests should be performed against the `unstable` branch.
|
||||
|
||||
@ -245,7 +247,7 @@ A few important fields in this structure are:
|
||||
* `server.db` is an array of Redis databases, where data is stored.
|
||||
* `server.commands` is the command table.
|
||||
* `server.clients` is a linked list of clients connected to the server.
|
||||
* `server.master` is a special client, the master, if the instance is a slave.
|
||||
* `server.master` is a special client, the master, if the instance is a replica.
|
||||
|
||||
There are tons of other fields. Most fields are commented directly inside
|
||||
the structure definition.
|
||||
@ -323,7 +325,7 @@ Inside server.c you can find code that handles other vital things of the Redis s
|
||||
networking.c
|
||||
---
|
||||
|
||||
This file defines all the I/O functions with clients, masters and slaves
|
||||
This file defines all the I/O functions with clients, masters and replicas
|
||||
(which in Redis are just special clients):
|
||||
|
||||
* `createClient()` allocates and initializes a new client.
|
||||
@ -390,21 +392,21 @@ replication.c
|
||||
|
||||
This is one of the most complex files inside Redis, it is recommended to
|
||||
approach it only after getting a bit familiar with the rest of the code base.
|
||||
In this file there is the implementation of both the master and slave role
|
||||
In this file there is the implementation of both the master and replica role
|
||||
of Redis.
|
||||
|
||||
One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing slave instances connected
|
||||
to our master, so that the slaves can get the writes performed by the clients:
|
||||
One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing replica instances connected
|
||||
to our master, so that the replicas can get the writes performed by the clients:
|
||||
this way their data set will remain synchronized with the one in the master.
|
||||
|
||||
This file also implements both the `SYNC` and `PSYNC` commands that are
|
||||
used in order to perform the first synchronization between masters and
|
||||
slaves, or to continue the replication after a disconnection.
|
||||
replicas, or to continue the replication after a disconnection.
|
||||
|
||||
Other C files
|
||||
---
|
||||
|
||||
* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c` and `t_zset.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
|
||||
* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c`, `t_zset.c` and `t_stream.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
|
||||
* `ae.c` implements the Redis event loop, it's a self contained library which is simple to read and understand.
|
||||
* `sds.c` is the Redis string library, check http://github.com/antirez/sds for more information.
|
||||
* `anet.c` is a library to use POSIX networking in a simpler way compared to the raw interface exposed by the kernel.
|
||||
|
12
deps/README.md
vendored
12
deps/README.md
vendored
@ -2,7 +2,6 @@ This directory contains all Redis dependencies, except for the libc that
|
||||
should be provided by the operating system.
|
||||
|
||||
* **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time.
|
||||
* **geohash-int** is inside the dependencies directory but is actually part of the Redis project, since it is our private fork (heavily modified) of a library initially developed for Ardb, which is in turn a fork of Redis.
|
||||
* **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed.
|
||||
* **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed.
|
||||
* **lua** is Lua 5.1 with minor changes for security and additional libraries.
|
||||
@ -22,7 +21,7 @@ just following tose steps:
|
||||
|
||||
1. Remove the jemalloc directory.
|
||||
2. Substitute it with the new jemalloc source tree.
|
||||
3. Edit the Makefile localted in the same directoy as the README you are
|
||||
3. Edit the Makefile localted in the same directory as the README you are
|
||||
reading, and change the --with-version in the Jemalloc configure script
|
||||
options with the version you are using. This is required because otherwise
|
||||
Jemalloc configuration script is broken and will not work nested in another
|
||||
@ -42,15 +41,10 @@ the following additional steps:
|
||||
changed, otherwise you could just copy the old implementation if you are
|
||||
upgrading just to a similar version of Jemalloc.
|
||||
|
||||
Geohash
|
||||
---
|
||||
|
||||
This is never upgraded since it's part of the Redis project. If there are changes to merge from Ardb there is the need to manually check differences, but at this point the source code is pretty different.
|
||||
|
||||
Hiredis
|
||||
---
|
||||
|
||||
Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is adviced to take a lot of care:
|
||||
Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is advised to take a lot of care:
|
||||
|
||||
1. Check with diff if hiredis API changed and what impact it could have in Redis.
|
||||
2. Make sure thet the SDS library inside Hiredis and inside Redis are compatible.
|
||||
@ -83,6 +77,6 @@ and our version:
|
||||
|
||||
1. Makefile is modified to allow a different compiler than GCC.
|
||||
2. We have the implementation source code, and directly link to the following external libraries: `lua_cjson.o`, `lua_struct.o`, `lua_cmsgpack.o` and `lua_bit.o`.
|
||||
3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode exectuion.
|
||||
3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode execution.
|
||||
|
||||
|
||||
|
6
deps/hiredis/.travis.yml
vendored
6
deps/hiredis/.travis.yml
vendored
@ -8,6 +8,12 @@ os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
branches:
|
||||
only:
|
||||
- staging
|
||||
- trying
|
||||
- master
|
||||
|
||||
before_script:
|
||||
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
|
||||
|
||||
|
53
deps/hiredis/CHANGELOG.md
vendored
53
deps/hiredis/CHANGELOG.md
vendored
@ -1,7 +1,51 @@
|
||||
### 1.0.0 (unreleased)
|
||||
|
||||
**Fixes**:
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
|
||||
protocol errors. This is consistent with the RESP specification. On 32-bit
|
||||
platforms, the upper bound is lowered to `SIZE_MAX`.
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well. If it was used to
|
||||
compare to other values, casting might be necessary or can be removed, if
|
||||
casting was applied before.
|
||||
|
||||
### 0.14.0 (2018-09-25)
|
||||
|
||||
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
|
||||
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
|
||||
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
|
||||
* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])
|
||||
* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])
|
||||
* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])
|
||||
* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])
|
||||
* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])
|
||||
* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])
|
||||
* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])
|
||||
* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])
|
||||
* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])
|
||||
* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])
|
||||
* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])
|
||||
* Fix libevent leak (zfz [515228])
|
||||
* Clean up GCC warning (Ichito Nagata [2ec774])
|
||||
* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])
|
||||
* Solaris compilation fix (Donald Whyte [41b07d])
|
||||
* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])
|
||||
* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])
|
||||
* libuv use after free fix (Paul Scott [cbb956])
|
||||
* Properly close socket fd on reconnect attempt (WSL [64d1ec])
|
||||
* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])
|
||||
* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])
|
||||
* Update libevent (Chris Xin [386802])
|
||||
* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])
|
||||
* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])
|
||||
* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])
|
||||
* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])
|
||||
* Compatibility fix for strerror_r (Tom Lee [bb1747])
|
||||
* Properly detect integer parse/overflow errors (Justin Brewer [93421f])
|
||||
* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])
|
||||
* Catch a buffer overflow when formatting the error message
|
||||
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
|
||||
* Fix warnings, when compiled with -Wshadow
|
||||
@ -9,11 +53,6 @@
|
||||
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well.
|
||||
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
|
||||
|
||||
* Remove backwards compatibility macro's
|
||||
|
||||
This removes the following old function aliases, use the new name now:
|
||||
@ -94,7 +133,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
|
||||
|
||||
* Add IPv6 support
|
||||
|
||||
* Remove possiblity of multiple close on same fd
|
||||
* Remove possibility of multiple close on same fd
|
||||
|
||||
* Add ability to bind source address on connect
|
||||
|
||||
|
24
deps/hiredis/Makefile
vendored
24
deps/hiredis/Makefile
vendored
@ -36,13 +36,13 @@ endef
|
||||
export REDIS_TEST_CONFIG
|
||||
|
||||
# Fallback to gcc when $CC is not in $PATH.
|
||||
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
CXX:=$(shell sh -c 'type $(CXX) >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
|
||||
DEBUG_FLAGS?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(ARCH)
|
||||
REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
|
||||
REAL_LDFLAGS=$(LDFLAGS)
|
||||
|
||||
DYLIBSUFFIX=so
|
||||
STLIBSUFFIX=a
|
||||
@ -58,12 +58,11 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
ifeq ($(uname_S),SunOS)
|
||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||
INSTALL= cp -r
|
||||
endif
|
||||
ifeq ($(uname_S),Darwin)
|
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
||||
@ -94,7 +93,7 @@ hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
|
||||
|
||||
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME)
|
||||
|
||||
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
|
||||
@ -161,11 +160,7 @@ clean:
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
INSTALL?= cp -r
|
||||
endif
|
||||
|
||||
INSTALL?= cp -a
|
||||
INSTALL?= cp -pPR
|
||||
|
||||
$(PKGCONFNAME): hiredis.h
|
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@ -181,8 +176,9 @@ $(PKGCONFNAME): hiredis.h
|
||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH)
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
|
||||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
||||
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
|
||||
|
4
deps/hiredis/adapters/libevent.h
vendored
4
deps/hiredis/adapters/libevent.h
vendored
@ -73,8 +73,8 @@ static void redisLibeventDelWrite(void *privdata) {
|
||||
|
||||
static void redisLibeventCleanup(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(e->rev);
|
||||
event_del(e->wev);
|
||||
event_free(e->rev);
|
||||
event_free(e->wev);
|
||||
free(e);
|
||||
}
|
||||
|
||||
|
9
deps/hiredis/adapters/libuv.h
vendored
9
deps/hiredis/adapters/libuv.h
vendored
@ -15,15 +15,12 @@ typedef struct redisLibuvEvents {
|
||||
|
||||
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||
int ev = (status ? p->events : events);
|
||||
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p->context != NULL && (events & UV_READABLE)) {
|
||||
if (p->context != NULL && (ev & UV_READABLE)) {
|
||||
redisAsyncHandleRead(p->context);
|
||||
}
|
||||
if (p->context != NULL && (events & UV_WRITABLE)) {
|
||||
if (p->context != NULL && (ev & UV_WRITABLE)) {
|
||||
redisAsyncHandleWrite(p->context);
|
||||
}
|
||||
}
|
||||
|
17
deps/hiredis/appveyor.yml
vendored
17
deps/hiredis/appveyor.yml
vendored
@ -1,24 +1,13 @@
|
||||
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
|
||||
environment:
|
||||
matrix:
|
||||
- CYG_ROOT: C:\cygwin64
|
||||
CYG_SETUP: setup-x86_64.exe
|
||||
CYG_MIRROR: http://cygwin.mirror.constant.com
|
||||
CYG_CACHE: C:\cygwin64\var\cache\setup
|
||||
CYG_BASH: C:\cygwin64\bin\bash
|
||||
- CYG_BASH: C:\cygwin64\bin\bash
|
||||
CC: gcc
|
||||
- CYG_ROOT: C:\cygwin
|
||||
CYG_SETUP: setup-x86.exe
|
||||
CYG_MIRROR: http://cygwin.mirror.constant.com
|
||||
CYG_CACHE: C:\cygwin\var\cache\setup
|
||||
CYG_BASH: C:\cygwin\bin\bash
|
||||
- CYG_BASH: C:\cygwin\bin\bash
|
||||
CC: gcc
|
||||
TARGET: 32bit
|
||||
TARGET_VARS: 32bit-vars
|
||||
|
||||
# Cache Cygwin files to speed up build
|
||||
cache:
|
||||
- '%CYG_CACHE%'
|
||||
clone_depth: 1
|
||||
|
||||
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
|
||||
@ -27,8 +16,6 @@ init:
|
||||
|
||||
# Install needed build dependencies
|
||||
install:
|
||||
- ps: 'Start-FileDownload "http://cygwin.com/$env:CYG_SETUP" -FileName "$env:CYG_SETUP"'
|
||||
- '%CYG_SETUP% --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages automake,bison,gcc-core,libtool,make,gettext-devel,gettext,intltool,pkg-config,clang,llvm > NUL 2>&1'
|
||||
- '%CYG_BASH% -lc "cygcheck -dc cygwin"'
|
||||
|
||||
build_script:
|
||||
|
67
deps/hiredis/async.c
vendored
67
deps/hiredis/async.c
vendored
@ -336,7 +336,8 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
|
||||
if (ac->err == 0) {
|
||||
/* For clean disconnects, there should be no pending callbacks. */
|
||||
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
|
||||
int ret = __redisShiftCallback(&ac->replies,NULL);
|
||||
assert(ret == REDIS_ERR);
|
||||
} else {
|
||||
/* Disconnection is caused by an error, make sure that pending
|
||||
* callbacks cannot call new commands. */
|
||||
@ -364,6 +365,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
|
||||
redisContext *c = &(ac->c);
|
||||
dict *callbacks;
|
||||
redisCallback *cb;
|
||||
dictEntry *de;
|
||||
int pvariant;
|
||||
char *stype;
|
||||
@ -387,16 +389,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
|
||||
de = dictFind(callbacks,sname);
|
||||
if (de != NULL) {
|
||||
memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
|
||||
cb = dictGetEntryVal(de);
|
||||
|
||||
/* If this is an subscribe reply decrease pending counter. */
|
||||
if (strcasecmp(stype+pvariant,"subscribe") == 0) {
|
||||
cb->pending_subs -= 1;
|
||||
}
|
||||
|
||||
memcpy(dstcb,cb,sizeof(*dstcb));
|
||||
|
||||
/* If this is an unsubscribe message, remove it. */
|
||||
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
|
||||
dictDelete(callbacks,sname);
|
||||
if (cb->pending_subs == 0)
|
||||
dictDelete(callbacks,sname);
|
||||
|
||||
/* If this was the last unsubscribe message, revert to
|
||||
* non-subscribe mode. */
|
||||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
|
||||
if (reply->element[2]->integer == 0)
|
||||
|
||||
/* Unset subscribed flag only when no pipelined pending subscribe. */
|
||||
if (reply->element[2]->integer == 0
|
||||
&& dictSize(ac->sub.channels) == 0
|
||||
&& dictSize(ac->sub.patterns) == 0)
|
||||
c->flags &= ~REDIS_SUBSCRIBED;
|
||||
}
|
||||
}
|
||||
@ -410,7 +424,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
|
||||
void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb = {NULL, NULL, NULL};
|
||||
redisCallback cb = {NULL, NULL, 0, NULL};
|
||||
void *reply = NULL;
|
||||
int status;
|
||||
|
||||
@ -492,22 +506,22 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
* write event fires. When connecting was not successful, the connect callback
|
||||
* is called with a REDIS_ERR status and the context is free'd. */
|
||||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
||||
int completed = 0;
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
if (redisCheckSocketError(c) == REDIS_ERR) {
|
||||
/* Try again later when connect(2) is still in progress. */
|
||||
if (errno == EINPROGRESS)
|
||||
return REDIS_OK;
|
||||
|
||||
if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
|
||||
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
|
||||
/* Error! */
|
||||
redisCheckSocketError(c);
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
|
||||
__redisAsyncDisconnect(ac);
|
||||
return REDIS_ERR;
|
||||
} else if (completed == 1) {
|
||||
/* connected! */
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Mark context as connected. */
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* This function should be called when the socket is readable.
|
||||
@ -583,6 +597,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len
|
||||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
struct dict *cbdict;
|
||||
dictEntry *de;
|
||||
redisCallback *existcb;
|
||||
int pvariant, hasnext;
|
||||
const char *cstr, *astr;
|
||||
size_t clen, alen;
|
||||
@ -596,6 +613,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
/* Setup callback */
|
||||
cb.fn = fn;
|
||||
cb.privdata = privdata;
|
||||
cb.pending_subs = 1;
|
||||
|
||||
/* Find out which command will be appended. */
|
||||
p = nextArgument(cmd,&cstr,&clen);
|
||||
@ -612,9 +630,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
||||
sname = sdsnewlen(astr,alen);
|
||||
if (pvariant)
|
||||
ret = dictReplace(ac->sub.patterns,sname,&cb);
|
||||
cbdict = ac->sub.patterns;
|
||||
else
|
||||
ret = dictReplace(ac->sub.channels,sname,&cb);
|
||||
cbdict = ac->sub.channels;
|
||||
|
||||
de = dictFind(cbdict,sname);
|
||||
|
||||
if (de != NULL) {
|
||||
existcb = dictGetEntryVal(de);
|
||||
cb.pending_subs = existcb->pending_subs + 1;
|
||||
}
|
||||
|
||||
ret = dictReplace(cbdict,sname,&cb);
|
||||
|
||||
if (ret == 0) sdsfree(sname);
|
||||
}
|
||||
@ -676,6 +703,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv
|
||||
int len;
|
||||
int status;
|
||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
||||
if (len < 0)
|
||||
return REDIS_ERR;
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
sdsfree(cmd);
|
||||
return status;
|
||||
|
5
deps/hiredis/async.h
vendored
5
deps/hiredis/async.h
vendored
@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
|
||||
typedef struct redisCallback {
|
||||
struct redisCallback *next; /* simple singly linked list */
|
||||
redisCallbackFn *fn;
|
||||
int pending_subs;
|
||||
void *privdata;
|
||||
} redisCallback;
|
||||
|
||||
@ -92,6 +93,10 @@ typedef struct redisAsyncContext {
|
||||
/* Regular command callbacks */
|
||||
redisCallbackList replies;
|
||||
|
||||
/* Address used for connect() */
|
||||
struct sockaddr *saddr;
|
||||
size_t addrlen;
|
||||
|
||||
/* Subscription callbacks */
|
||||
struct {
|
||||
redisCallbackList invalid;
|
||||
|
19
deps/hiredis/fmacros.h
vendored
19
deps/hiredis/fmacros.h
vendored
@ -1,25 +1,12 @@
|
||||
#ifndef __HIREDIS_FMACRO_H
|
||||
#define __HIREDIS_FMACRO_H
|
||||
|
||||
#if defined(__linux__)
|
||||
#define _BSD_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
#include <sys/cdefs.h>
|
||||
#endif
|
||||
|
||||
#if defined(__sun__)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#else
|
||||
#if !(defined(__APPLE__) && defined(__MACH__)) && !(defined(__FreeBSD__))
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#endif
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#define _OSX
|
||||
/* Enable TCP_KEEPALIVE */
|
||||
#define _DARWIN_C_SOURCE
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
125
deps/hiredis/hiredis.c
vendored
125
deps/hiredis/hiredis.c
vendored
@ -47,7 +47,9 @@ static redisReply *createReplyObject(int type);
|
||||
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
|
||||
static void *createArrayObject(const redisReadTask *task, int elements);
|
||||
static void *createIntegerObject(const redisReadTask *task, long long value);
|
||||
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
|
||||
static void *createNilObject(const redisReadTask *task);
|
||||
static void *createBoolObject(const redisReadTask *task, int bval);
|
||||
|
||||
/* Default set of functions to build the reply. Keep in mind that such a
|
||||
* function returning NULL is interpreted as OOM. */
|
||||
@ -55,7 +57,9 @@ static redisReplyObjectFunctions defaultFunctions = {
|
||||
createStringObject,
|
||||
createArrayObject,
|
||||
createIntegerObject,
|
||||
createDoubleObject,
|
||||
createNilObject,
|
||||
createBoolObject,
|
||||
freeReplyObject
|
||||
};
|
||||
|
||||
@ -82,18 +86,19 @@ void freeReplyObject(void *reply) {
|
||||
case REDIS_REPLY_INTEGER:
|
||||
break; /* Nothing to free */
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
if (r->element != NULL) {
|
||||
for (j = 0; j < r->elements; j++)
|
||||
if (r->element[j] != NULL)
|
||||
freeReplyObject(r->element[j]);
|
||||
freeReplyObject(r->element[j]);
|
||||
free(r->element);
|
||||
}
|
||||
break;
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_STRING:
|
||||
if (r->str != NULL)
|
||||
free(r->str);
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
free(r->str);
|
||||
break;
|
||||
}
|
||||
free(r);
|
||||
@ -125,7 +130,9 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -134,7 +141,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
static void *createArrayObject(const redisReadTask *task, int elements) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
r = createReplyObject(REDIS_REPLY_ARRAY);
|
||||
r = createReplyObject(task->type);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
@ -150,7 +157,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -167,7 +176,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
r = createReplyObject(REDIS_REPLY_DOUBLE);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->dval = value;
|
||||
r->str = malloc(len+1);
|
||||
if (r->str == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* The double reply also has the original protocol string representing a
|
||||
* double as a null terminated string. This way the caller does not need
|
||||
* to format back for string conversion, especially since Redis does efforts
|
||||
* to make the string more human readable avoiding the calssical double
|
||||
* decimal string conversion artifacts. */
|
||||
memcpy(r->str, str, len);
|
||||
r->str[len] = '\0';
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -182,7 +225,28 @@ static void *createNilObject(const redisReadTask *task) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createBoolObject(const redisReadTask *task, int bval) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
r = createReplyObject(REDIS_REPLY_BOOL);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->integer = bval != 0;
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@ -432,11 +496,7 @@ cleanup:
|
||||
}
|
||||
|
||||
sdsfree(curarg);
|
||||
|
||||
/* No need to check cmd since it is the last statement that can fail,
|
||||
* but do it anyway to be as defensive as possible. */
|
||||
if (cmd != NULL)
|
||||
free(cmd);
|
||||
free(cmd);
|
||||
|
||||
return error_type;
|
||||
}
|
||||
@ -581,7 +641,7 @@ void __redisSetError(redisContext *c, int type, const char *str) {
|
||||
} else {
|
||||
/* Only REDIS_ERR_IO may lack a description! */
|
||||
assert(type == REDIS_ERR_IO);
|
||||
__redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
|
||||
strerror_r(errno, c->errstr, sizeof(c->errstr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -596,14 +656,8 @@ static redisContext *redisContextInit(void) {
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->err = 0;
|
||||
c->errstr[0] = '\0';
|
||||
c->obuf = sdsempty();
|
||||
c->reader = redisReaderCreate();
|
||||
c->tcp.host = NULL;
|
||||
c->tcp.source_addr = NULL;
|
||||
c->unix_sock.path = NULL;
|
||||
c->timeout = NULL;
|
||||
|
||||
if (c->obuf == NULL || c->reader == NULL) {
|
||||
redisFree(c);
|
||||
@ -618,18 +672,14 @@ void redisFree(redisContext *c) {
|
||||
return;
|
||||
if (c->fd > 0)
|
||||
close(c->fd);
|
||||
if (c->obuf != NULL)
|
||||
sdsfree(c->obuf);
|
||||
if (c->reader != NULL)
|
||||
redisReaderFree(c->reader);
|
||||
if (c->tcp.host)
|
||||
free(c->tcp.host);
|
||||
if (c->tcp.source_addr)
|
||||
free(c->tcp.source_addr);
|
||||
if (c->unix_sock.path)
|
||||
free(c->unix_sock.path);
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
|
||||
sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
free(c->tcp.host);
|
||||
free(c->tcp.source_addr);
|
||||
free(c->unix_sock.path);
|
||||
free(c->timeout);
|
||||
free(c->saddr);
|
||||
free(c);
|
||||
}
|
||||
|
||||
@ -710,6 +760,8 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
|
||||
redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
return c;
|
||||
@ -718,6 +770,8 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_REUSEADDR;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
@ -789,7 +843,7 @@ int redisEnableKeepAlive(redisContext *c) {
|
||||
/* Use this function to handle a read event on the descriptor. It will try
|
||||
* and read some bytes from the socket and feed them to the reply parser.
|
||||
*
|
||||
* After this function is called, you may use redisContextReadReply to
|
||||
* After this function is called, you may use redisGetReplyFromReader to
|
||||
* see if there is a reply available. */
|
||||
int redisBufferRead(redisContext *c) {
|
||||
char buf[1024*16];
|
||||
@ -1007,9 +1061,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) {
|
||||
|
||||
void *redisCommand(redisContext *c, const char *format, ...) {
|
||||
va_list ap;
|
||||
void *reply = NULL;
|
||||
va_start(ap,format);
|
||||
reply = redisvCommand(c,format,ap);
|
||||
void *reply = redisvCommand(c,format,ap);
|
||||
va_end(ap);
|
||||
return reply;
|
||||
}
|
||||
|
37
deps/hiredis/hiredis.h
vendored
37
deps/hiredis/hiredis.h
vendored
@ -40,9 +40,9 @@
|
||||
#include "sds.h" /* for sds */
|
||||
|
||||
#define HIREDIS_MAJOR 0
|
||||
#define HIREDIS_MINOR 13
|
||||
#define HIREDIS_PATCH 3
|
||||
#define HIREDIS_SONAME 0.13
|
||||
#define HIREDIS_MINOR 14
|
||||
#define HIREDIS_PATCH 0
|
||||
#define HIREDIS_SONAME 0.14
|
||||
|
||||
/* Connection type can be blocking or non-blocking and is set in the
|
||||
* least significant bit of the flags field in redisContext. */
|
||||
@ -80,30 +80,6 @@
|
||||
* SO_REUSEADDR is being used. */
|
||||
#define REDIS_CONNECT_RETRIES 10
|
||||
|
||||
/* strerror_r has two completely different prototypes and behaviors
|
||||
* depending on system issues, so we need to operate on the error buffer
|
||||
* differently depending on which strerror_r we're using. */
|
||||
#ifndef _GNU_SOURCE
|
||||
/* "regular" POSIX strerror_r that does the right thing. */
|
||||
#define __redis_strerror_r(errno, buf, len) \
|
||||
do { \
|
||||
strerror_r((errno), (buf), (len)); \
|
||||
} while (0)
|
||||
#else
|
||||
/* "bad" GNU strerror_r we need to clean up after. */
|
||||
#define __redis_strerror_r(errno, buf, len) \
|
||||
do { \
|
||||
char *err_str = strerror_r((errno), (buf), (len)); \
|
||||
/* If return value _isn't_ the start of the buffer we passed in, \
|
||||
* then GNU strerror_r returned an internal static buffer and we \
|
||||
* need to copy the result into our private buffer. */ \
|
||||
if (err_str != (buf)) { \
|
||||
strncpy((buf), err_str, ((len) - 1)); \
|
||||
buf[(len)-1] = '\0'; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -112,8 +88,10 @@ extern "C" {
|
||||
typedef struct redisReply {
|
||||
int type; /* REDIS_REPLY_* */
|
||||
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
|
||||
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
|
||||
size_t len; /* Length of string */
|
||||
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
|
||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||
} redisReply;
|
||||
@ -158,6 +136,9 @@ typedef struct redisContext {
|
||||
char *path;
|
||||
} unix_sock;
|
||||
|
||||
/* For non-blocking connect */
|
||||
struct sockadr *saddr;
|
||||
size_t addrlen;
|
||||
} redisContext;
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
|
75
deps/hiredis/net.c
vendored
75
deps/hiredis/net.c
vendored
@ -65,12 +65,13 @@ static void redisContextCloseFd(redisContext *c) {
|
||||
}
|
||||
|
||||
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
|
||||
int errorno = errno; /* snprintf() may change errno */
|
||||
char buf[128] = { 0 };
|
||||
size_t len = 0;
|
||||
|
||||
if (prefix != NULL)
|
||||
len = snprintf(buf,sizeof(buf),"%s: ",prefix);
|
||||
__redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
|
||||
strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
|
||||
__redisSetError(c,type,buf);
|
||||
}
|
||||
|
||||
@ -135,14 +136,13 @@ int redisKeepAlive(redisContext *c, int interval) {
|
||||
|
||||
val = interval;
|
||||
|
||||
#ifdef _OSX
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
#else
|
||||
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
|
||||
val = interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
return REDIS_ERR;
|
||||
@ -221,8 +221,10 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (redisCheckSocketError(c) != REDIS_OK)
|
||||
if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
|
||||
redisCheckSocketError(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
@ -232,8 +234,28 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
int redisCheckConnectDone(redisContext *c, int *completed) {
|
||||
int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
|
||||
if (rc == 0) {
|
||||
*completed = 1;
|
||||
return REDIS_OK;
|
||||
}
|
||||
switch (errno) {
|
||||
case EISCONN:
|
||||
*completed = 1;
|
||||
return REDIS_OK;
|
||||
case EALREADY:
|
||||
case EINPROGRESS:
|
||||
case EWOULDBLOCK:
|
||||
*completed = 0;
|
||||
return REDIS_OK;
|
||||
default:
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
int redisCheckSocketError(redisContext *c) {
|
||||
int err = 0;
|
||||
int err = 0, errno_saved = errno;
|
||||
socklen_t errlen = sizeof(err);
|
||||
|
||||
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
|
||||
@ -241,6 +263,10 @@ int redisCheckSocketError(redisContext *c) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (err == 0) {
|
||||
err = errno_saved;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
errno = err;
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
@ -285,8 +311,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
* This is a bit ugly, but atleast it works and doesn't leak memory.
|
||||
**/
|
||||
if (c->tcp.host != addr) {
|
||||
if (c->tcp.host)
|
||||
free(c->tcp.host);
|
||||
free(c->tcp.host);
|
||||
|
||||
c->tcp.host = strdup(addr);
|
||||
}
|
||||
@ -299,8 +324,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
} else {
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
}
|
||||
|
||||
@ -356,6 +380,7 @@ addrretry:
|
||||
n = 1;
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
|
||||
sizeof(n)) < 0) {
|
||||
freeaddrinfo(bservinfo);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@ -374,12 +399,27 @@ addrretry:
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* For repeat connection */
|
||||
if (c->saddr) {
|
||||
free(c->saddr);
|
||||
}
|
||||
c->saddr = malloc(p->ai_addrlen);
|
||||
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
||||
c->addrlen = p->ai_addrlen;
|
||||
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
if (errno == EHOSTUNREACH) {
|
||||
redisContextCloseFd(c);
|
||||
continue;
|
||||
} else if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else if (errno == EINPROGRESS) {
|
||||
if (blocking) {
|
||||
goto wait_for_ready;
|
||||
}
|
||||
/* This is ok.
|
||||
* Note that even when it's in blocking mode, we unset blocking
|
||||
* for `connect()`
|
||||
*/
|
||||
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
|
||||
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
||||
goto error;
|
||||
@ -388,6 +428,7 @@ addrretry:
|
||||
goto addrretry;
|
||||
}
|
||||
} else {
|
||||
wait_for_ready:
|
||||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||
goto error;
|
||||
}
|
||||
@ -411,7 +452,10 @@ addrretry:
|
||||
error:
|
||||
rv = REDIS_ERR;
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
if(servinfo) {
|
||||
freeaddrinfo(servinfo);
|
||||
}
|
||||
|
||||
return rv; // Need to return REDIS_OK if alright
|
||||
}
|
||||
|
||||
@ -431,7 +475,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
struct sockaddr_un sa;
|
||||
long timeout_msec = -1;
|
||||
|
||||
if (redisCreateSocket(c,AF_LOCAL) < 0)
|
||||
if (redisCreateSocket(c,AF_UNIX) < 0)
|
||||
return REDIS_ERR;
|
||||
if (redisSetBlocking(c,0) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
@ -448,15 +492,14 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
} else {
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
}
|
||||
|
||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa.sun_family = AF_LOCAL;
|
||||
sa.sun_family = AF_UNIX;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
|
5
deps/hiredis/net.h
vendored
5
deps/hiredis/net.h
vendored
@ -37,10 +37,6 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
int redisCheckSocketError(redisContext *c);
|
||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
||||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
|
||||
@ -49,5 +45,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
||||
const char *source_addr);
|
||||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
|
||||
int redisKeepAlive(redisContext *c, int interval);
|
||||
int redisCheckConnectDone(redisContext *c, int *completed);
|
||||
|
||||
#endif
|
||||
|
234
deps/hiredis/read.c
vendored
234
deps/hiredis/read.c
vendored
@ -29,9 +29,9 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdlib.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
@ -39,6 +39,8 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "read.h"
|
||||
#include "sds.h"
|
||||
@ -52,11 +54,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
}
|
||||
|
||||
/* Clear input buffer on errors. */
|
||||
if (r->buf != NULL) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
}
|
||||
sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
|
||||
/* Reset task stack. */
|
||||
r->ridx = -1;
|
||||
@ -143,33 +143,79 @@ static char *seekNewline(char *s, size_t len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read a long long value starting at *s, under the assumption that it will be
|
||||
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
|
||||
static long long readLongLong(char *s) {
|
||||
long long v = 0;
|
||||
int dec, mult = 1;
|
||||
char c;
|
||||
/* Convert a string into a long long. Returns REDIS_OK if the string could be
|
||||
* parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
|
||||
* will be set to the parsed value when appropriate.
|
||||
*
|
||||
* Note that this function demands that the string strictly represents
|
||||
* a long long: no spaces or other characters before or after the string
|
||||
* representing the number are accepted, nor zeroes at the start if not
|
||||
* for the string "0" representing the zero number.
|
||||
*
|
||||
* Because of its strictness, it is safe to use this function to check if
|
||||
* you can convert a string into a long long, and obtain back the string
|
||||
* from the number without any loss in the string representation. */
|
||||
static int string2ll(const char *s, size_t slen, long long *value) {
|
||||
const char *p = s;
|
||||
size_t plen = 0;
|
||||
int negative = 0;
|
||||
unsigned long long v;
|
||||
|
||||
if (*s == '-') {
|
||||
mult = -1;
|
||||
s++;
|
||||
} else if (*s == '+') {
|
||||
mult = 1;
|
||||
s++;
|
||||
if (plen == slen)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Special case: first and only digit is 0. */
|
||||
if (slen == 1 && p[0] == '0') {
|
||||
if (value != NULL) *value = 0;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
while ((c = *(s++)) != '\r') {
|
||||
dec = c - '0';
|
||||
if (dec >= 0 && dec < 10) {
|
||||
v *= 10;
|
||||
v += dec;
|
||||
} else {
|
||||
/* Should not happen... */
|
||||
return -1;
|
||||
}
|
||||
if (p[0] == '-') {
|
||||
negative = 1;
|
||||
p++; plen++;
|
||||
|
||||
/* Abort on only a negative sign. */
|
||||
if (plen == slen)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
return mult*v;
|
||||
/* First digit should be 1-9, otherwise the string should just be 0. */
|
||||
if (p[0] >= '1' && p[0] <= '9') {
|
||||
v = p[0]-'0';
|
||||
p++; plen++;
|
||||
} else if (p[0] == '0' && slen == 1) {
|
||||
*value = 0;
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
|
||||
if (v > (ULLONG_MAX / 10)) /* Overflow. */
|
||||
return REDIS_ERR;
|
||||
v *= 10;
|
||||
|
||||
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
|
||||
return REDIS_ERR;
|
||||
v += p[0]-'0';
|
||||
|
||||
p++; plen++;
|
||||
}
|
||||
|
||||
/* Return if not all bytes were used. */
|
||||
if (plen < slen)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (negative) {
|
||||
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
|
||||
return REDIS_ERR;
|
||||
if (value != NULL) *value = -v;
|
||||
} else {
|
||||
if (v > LLONG_MAX) /* Overflow. */
|
||||
return REDIS_ERR;
|
||||
if (value != NULL) *value = v;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static char *readLine(redisReader *r, int *_len) {
|
||||
@ -198,7 +244,9 @@ static void moveToNextTask(redisReader *r) {
|
||||
|
||||
cur = &(r->rstack[r->ridx]);
|
||||
prv = &(r->rstack[r->ridx-1]);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY ||
|
||||
prv->type == REDIS_REPLY_MAP ||
|
||||
prv->type == REDIS_REPLY_SET);
|
||||
if (cur->idx == prv->elements-1) {
|
||||
r->ridx--;
|
||||
} else {
|
||||
@ -220,10 +268,58 @@ static int processLineItem(redisReader *r) {
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||
if (r->fn && r->fn->createInteger)
|
||||
obj = r->fn->createInteger(cur,readLongLong(p));
|
||||
else
|
||||
if (r->fn && r->fn->createInteger) {
|
||||
long long v;
|
||||
if (string2ll(p, len, &v) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad integer value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
obj = r->fn->createInteger(cur,v);
|
||||
} else {
|
||||
obj = (void*)REDIS_REPLY_INTEGER;
|
||||
}
|
||||
} else if (cur->type == REDIS_REPLY_DOUBLE) {
|
||||
if (r->fn && r->fn->createDouble) {
|
||||
char buf[326], *eptr;
|
||||
double d;
|
||||
|
||||
if ((size_t)len >= sizeof(buf)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Double value is too large");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
memcpy(buf,p,len);
|
||||
buf[len] = '\0';
|
||||
|
||||
if (strcasecmp(buf,",inf") == 0) {
|
||||
d = 1.0/0.0; /* Positive infinite. */
|
||||
} else if (strcasecmp(buf,",-inf") == 0) {
|
||||
d = -1.0/0.0; /* Nevative infinite. */
|
||||
} else {
|
||||
d = strtod((char*)buf,&eptr);
|
||||
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad double value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
obj = r->fn->createDouble(cur,d,buf,len);
|
||||
} else {
|
||||
obj = (void*)REDIS_REPLY_DOUBLE;
|
||||
}
|
||||
} else if (cur->type == REDIS_REPLY_NIL) {
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_NIL;
|
||||
} else if (cur->type == REDIS_REPLY_BOOL) {
|
||||
int bval = p[0] == 't' || p[0] == 'T';
|
||||
if (r->fn && r->fn->createBool)
|
||||
obj = r->fn->createBool(cur,bval);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_BOOL;
|
||||
} else {
|
||||
/* Type will be error or status. */
|
||||
if (r->fn && r->fn->createString)
|
||||
@ -250,7 +346,7 @@ static int processBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj = NULL;
|
||||
char *p, *s;
|
||||
long len;
|
||||
long long len;
|
||||
unsigned long bytelen;
|
||||
int success = 0;
|
||||
|
||||
@ -259,9 +355,20 @@ static int processBulkItem(redisReader *r) {
|
||||
if (s != NULL) {
|
||||
p = r->buf+r->pos;
|
||||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||
len = readLongLong(p);
|
||||
|
||||
if (len < 0) {
|
||||
if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad bulk string length");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bulk string length out of range");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (len == -1) {
|
||||
/* The nil object can always be created. */
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
@ -299,12 +406,13 @@ static int processBulkItem(redisReader *r) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int processMultiBulkItem(redisReader *r) {
|
||||
/* Process the array, map and set types. */
|
||||
static int processAggregateItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
long elements;
|
||||
int root = 0;
|
||||
long long elements;
|
||||
int root = 0, len;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 7 */
|
||||
if (r->ridx == 8) {
|
||||
@ -313,10 +421,21 @@ static int processMultiBulkItem(redisReader *r) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if ((p = readLine(r,NULL)) != NULL) {
|
||||
elements = readLongLong(p);
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (string2ll(p, len, &elements) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad multi-bulk length");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
root = (r->ridx == 0);
|
||||
|
||||
if (elements < -1 || elements > INT_MAX) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Multi-bulk length out of range");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (elements == -1) {
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
@ -330,10 +449,12 @@ static int processMultiBulkItem(redisReader *r) {
|
||||
|
||||
moveToNextTask(r);
|
||||
} else {
|
||||
if (cur->type == REDIS_REPLY_MAP) elements *= 2;
|
||||
|
||||
if (r->fn && r->fn->createArray)
|
||||
obj = r->fn->createArray(cur,elements);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_ARRAY;
|
||||
obj = (void*)(long)cur->type;
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
@ -381,12 +502,27 @@ static int processItem(redisReader *r) {
|
||||
case ':':
|
||||
cur->type = REDIS_REPLY_INTEGER;
|
||||
break;
|
||||
case ',':
|
||||
cur->type = REDIS_REPLY_DOUBLE;
|
||||
break;
|
||||
case '_':
|
||||
cur->type = REDIS_REPLY_NIL;
|
||||
break;
|
||||
case '$':
|
||||
cur->type = REDIS_REPLY_STRING;
|
||||
break;
|
||||
case '*':
|
||||
cur->type = REDIS_REPLY_ARRAY;
|
||||
break;
|
||||
case '%':
|
||||
cur->type = REDIS_REPLY_MAP;
|
||||
break;
|
||||
case '~':
|
||||
cur->type = REDIS_REPLY_SET;
|
||||
break;
|
||||
case '#':
|
||||
cur->type = REDIS_REPLY_BOOL;
|
||||
break;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
@ -402,11 +538,16 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_INTEGER:
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
case REDIS_REPLY_NIL:
|
||||
case REDIS_REPLY_BOOL:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
return processBulkItem(r);
|
||||
case REDIS_REPLY_ARRAY:
|
||||
return processMultiBulkItem(r);
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
return processAggregateItem(r);
|
||||
default:
|
||||
assert(NULL);
|
||||
return REDIS_ERR; /* Avoid warning. */
|
||||
@ -416,12 +557,10 @@ static int processItem(redisReader *r) {
|
||||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||
redisReader *r;
|
||||
|
||||
r = calloc(sizeof(redisReader),1);
|
||||
r = calloc(1,sizeof(redisReader));
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->err = 0;
|
||||
r->errstr[0] = '\0';
|
||||
r->fn = fn;
|
||||
r->buf = sdsempty();
|
||||
r->maxbuf = REDIS_READER_MAX_BUF;
|
||||
@ -435,10 +574,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||
}
|
||||
|
||||
void redisReaderFree(redisReader *r) {
|
||||
if (r == NULL)
|
||||
return;
|
||||
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
||||
r->fn->freeObject(r->reply);
|
||||
if (r->buf != NULL)
|
||||
sdsfree(r->buf);
|
||||
sdsfree(r->buf);
|
||||
free(r);
|
||||
}
|
||||
|
||||
|
10
deps/hiredis/read.h
vendored
10
deps/hiredis/read.h
vendored
@ -53,6 +53,14 @@
|
||||
#define REDIS_REPLY_NIL 4
|
||||
#define REDIS_REPLY_STATUS 5
|
||||
#define REDIS_REPLY_ERROR 6
|
||||
#define REDIS_REPLY_DOUBLE 7
|
||||
#define REDIS_REPLY_BOOL 8
|
||||
#define REDIS_REPLY_VERB 9
|
||||
#define REDIS_REPLY_MAP 9
|
||||
#define REDIS_REPLY_SET 10
|
||||
#define REDIS_REPLY_ATTR 11
|
||||
#define REDIS_REPLY_PUSH 12
|
||||
#define REDIS_REPLY_BIGNUM 13
|
||||
|
||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
||||
|
||||
@ -73,7 +81,9 @@ typedef struct redisReplyObjectFunctions {
|
||||
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, int);
|
||||
void *(*createInteger)(const redisReadTask*, long long);
|
||||
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
|
||||
void *(*createNil)(const redisReadTask*);
|
||||
void *(*createBool)(const redisReadTask*, int);
|
||||
void (*freeObject)(void*);
|
||||
} redisReplyObjectFunctions;
|
||||
|
||||
|
29
deps/hiredis/sds.c
vendored
29
deps/hiredis/sds.c
vendored
@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
hdrlen = sdsHdrSize(type);
|
||||
if (oldtype==type) {
|
||||
newsh = s_realloc(sh, hdrlen+newlen+1);
|
||||
if (newsh == NULL) return NULL;
|
||||
if (newsh == NULL) {
|
||||
s_free(sh);
|
||||
return NULL;
|
||||
}
|
||||
s = (char*)newsh+hdrlen;
|
||||
} else {
|
||||
/* Since the header size changes, need to move the string forward,
|
||||
@ -592,6 +595,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
/* Make sure there is always space for at least 1 char. */
|
||||
if (sdsavail(s)==0) {
|
||||
s = sdsMakeRoomFor(s,1);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
|
||||
switch(*f) {
|
||||
@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = (next == 's') ? strlen(str) : sdslen(str);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,str,l);
|
||||
sdsinclen(s,l);
|
||||
@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = sdsll2str(buf,num);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sdsinclen(s,l);
|
||||
@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = sdsull2str(buf,unum);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sdsinclen(s,l);
|
||||
@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
/* Add null-term */
|
||||
s[i] = '\0';
|
||||
return s;
|
||||
|
||||
fmt_error:
|
||||
va_end(ap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove the part of the string from left and from right composed just of
|
||||
@ -1018,10 +1029,18 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
{
|
||||
char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
if (new_vector == NULL) {
|
||||
s_free(vector);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vector = new_vector;
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
}
|
||||
} else {
|
||||
/* Even on empty input string return something not NULL. */
|
||||
if (vector == NULL) vector = s_malloc(sizeof(void*));
|
||||
|
154
deps/hiredis/test.c
vendored
154
deps/hiredis/test.c
vendored
@ -3,7 +3,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <netdb.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
@ -91,7 +93,7 @@ static int disconnect(redisContext *c, int keep_fd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static redisContext *connect(struct config config) {
|
||||
static redisContext *do_connect(struct config config) {
|
||||
redisContext *c = NULL;
|
||||
|
||||
if (config.type == CONN_TCP) {
|
||||
@ -248,7 +250,7 @@ static void test_append_formatted_commands(struct config config) {
|
||||
char *cmd;
|
||||
int len;
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
|
||||
test("Append format command: ");
|
||||
|
||||
@ -302,6 +304,82 @@ static void test_reply_reader(void) {
|
||||
strncasecmp(reader->errstr,"No support for",14) == 0);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses LLONG_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":9223372036854775807\r\n",22);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->integer == LLONG_MAX);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when > LLONG_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":9223372036854775808\r\n",22);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad integer value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses LLONG_MIN: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->integer == LLONG_MIN);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when < LLONG_MIN: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad integer value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when array < -1: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when bulk < -1: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when array > INT_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when bulk > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
#endif
|
||||
|
||||
test("Works with NULL functions for reply: ");
|
||||
reader = redisReaderCreate();
|
||||
reader->fn = NULL;
|
||||
@ -358,18 +436,32 @@ static void test_free_null(void) {
|
||||
|
||||
static void test_blocking_connection_errors(void) {
|
||||
redisContext *c;
|
||||
struct addrinfo hints = {.ai_family = AF_INET};
|
||||
struct addrinfo *ai_tmp = NULL;
|
||||
const char *bad_domain = "idontexist.com";
|
||||
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
c = redisConnect((char*)"idontexist.test", 6379);
|
||||
test_cond(c->err == REDIS_ERR_OTHER &&
|
||||
(strcmp(c->errstr,"Name or service not known") == 0 ||
|
||||
strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
|
||||
strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr,"No address associated with hostname") == 0 ||
|
||||
strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
|
||||
strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr,"no address associated with name") == 0));
|
||||
redisFree(c);
|
||||
int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp);
|
||||
if (rv != 0) {
|
||||
// Address does *not* exist
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
// First see if this domain name *actually* resolves to NXDOMAIN
|
||||
c = redisConnect("dontexist.com", 6379);
|
||||
test_cond(
|
||||
c->err == REDIS_ERR_OTHER &&
|
||||
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
||||
strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
||||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "no address associated with name") == 0));
|
||||
redisFree(c);
|
||||
} else {
|
||||
printf("Skipping NXDOMAIN test. Found evil ISP!\n");
|
||||
freeaddrinfo(ai_tmp);
|
||||
}
|
||||
|
||||
test("Returns error when the port is not open: ");
|
||||
c = redisConnect((char*)"localhost", 1);
|
||||
@ -387,7 +479,7 @@ static void test_blocking_connection(struct config config) {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
|
||||
test("Is able to deliver commands: ");
|
||||
reply = redisCommand(c,"PING");
|
||||
@ -468,7 +560,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
const char *cmd = "DEBUG SLEEP 3\r\n";
|
||||
struct timeval tv;
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
test("Successfully completes a command when the timeout is not exceeded: ");
|
||||
reply = redisCommand(c,"SET foo fast");
|
||||
freeReplyObject(reply);
|
||||
@ -480,7 +572,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
freeReplyObject(reply);
|
||||
disconnect(c, 0);
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
test("Does not return a reply when the command times out: ");
|
||||
s = write(c->fd, cmd, strlen(cmd));
|
||||
tv.tv_sec = 0;
|
||||
@ -514,7 +606,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
int major, minor;
|
||||
|
||||
/* Connect to target given by config. */
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
{
|
||||
/* Find out Redis version to determine the path for the next test */
|
||||
const char *field = "redis_version:";
|
||||
@ -549,7 +641,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||
redisFree(c);
|
||||
|
||||
c = connect(config);
|
||||
c = do_connect(config);
|
||||
test("Returns I/O error on socket timeout: ");
|
||||
struct timeval tv = { 0, 1000 };
|
||||
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
||||
@ -583,7 +675,7 @@ static void test_invalid_timeout_errors(struct config config) {
|
||||
}
|
||||
|
||||
static void test_throughput(struct config config) {
|
||||
redisContext *c = connect(config);
|
||||
redisContext *c = do_connect(config);
|
||||
redisReply **replies;
|
||||
int i, num;
|
||||
long long t1, t2;
|
||||
@ -616,6 +708,17 @@ static void test_throughput(struct config config) {
|
||||
free(replies);
|
||||
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
num = 10000;
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
@ -644,6 +747,19 @@ static void test_throughput(struct config config) {
|
||||
free(replies);
|
||||
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
disconnect(c, 0);
|
||||
}
|
||||
|
||||
|
@ -215,4 +215,32 @@ ixalloc(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t extra,
|
||||
return arena_ralloc_no_move(tsdn, ptr, oldsize, size, extra, zero);
|
||||
}
|
||||
|
||||
JEMALLOC_ALWAYS_INLINE int
|
||||
iget_defrag_hint(tsdn_t *tsdn, void* ptr, int *bin_util, int *run_util) {
|
||||
int defrag = 0;
|
||||
rtree_ctx_t rtree_ctx_fallback;
|
||||
rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
|
||||
szind_t szind;
|
||||
bool is_slab;
|
||||
rtree_szind_slab_read(tsdn, &extents_rtree, rtree_ctx, (uintptr_t)ptr, true, &szind, &is_slab);
|
||||
if (likely(is_slab)) {
|
||||
/* Small allocation. */
|
||||
extent_t *slab = iealloc(tsdn, ptr);
|
||||
arena_t *arena = extent_arena_get(slab);
|
||||
szind_t binind = extent_szind_get(slab);
|
||||
bin_t *bin = &arena->bins[binind];
|
||||
malloc_mutex_lock(tsdn, &bin->lock);
|
||||
/* don't bother moving allocations from the slab currently used for new allocations */
|
||||
if (slab != bin->slabcur) {
|
||||
const bin_info_t *bin_info = &bin_infos[binind];
|
||||
size_t availregs = bin_info->nregs * bin->stats.curslabs;
|
||||
*bin_util = ((long long)bin->stats.curregs<<16) / availregs;
|
||||
*run_util = ((long long)(bin_info->nregs - extent_nfree_get(slab))<<16) / bin_info->nregs;
|
||||
defrag = 1;
|
||||
}
|
||||
malloc_mutex_unlock(tsdn, &bin->lock);
|
||||
}
|
||||
return defrag;
|
||||
}
|
||||
|
||||
#endif /* JEMALLOC_INTERNAL_INLINES_C_H */
|
||||
|
@ -120,3 +120,7 @@
|
||||
# define JEMALLOC_RESTRICT_RETURN
|
||||
# define JEMALLOC_ALLOCATOR
|
||||
#endif
|
||||
|
||||
/* This version of Jemalloc, modified for Redis, has the je_get_defrag_hint()
|
||||
* function. */
|
||||
#define JEMALLOC_FRAG_HINT
|
||||
|
11
deps/jemalloc/src/jemalloc.c
vendored
11
deps/jemalloc/src/jemalloc.c
vendored
@ -3324,3 +3324,14 @@ jemalloc_postfork_child(void) {
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/* Helps the application decide if a pointer is worth re-allocating in order to reduce fragmentation.
|
||||
* returns 0 if the allocation is in the currently active run,
|
||||
* or when it is not causing any frag issue (large or huge bin)
|
||||
* returns the bin utilization and run utilization both in fixed point 16:16.
|
||||
* If the application decides to re-allocate it should use MALLOCX_TCACHE_NONE when doing so. */
|
||||
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
|
||||
get_defrag_hint(void* ptr, int *bin_util, int *run_util) {
|
||||
assert(ptr != NULL);
|
||||
return iget_defrag_hint(TSDN_NULL, ptr, bin_util, run_util);
|
||||
}
|
||||
|
569
redis.conf
569
redis.conf
@ -264,86 +264,100 @@ dir ./
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
|
||||
# Master-Replica replication. Use replicaof to make a Redis instance a copy of
|
||||
# another Redis server. A few things to understand ASAP about Redis replication.
|
||||
#
|
||||
# +------------------+ +---------------+
|
||||
# | Master | ---> | Replica |
|
||||
# | (receive writes) | | (exact copy) |
|
||||
# +------------------+ +---------------+
|
||||
#
|
||||
# 1) Redis replication is asynchronous, but you can configure a master to
|
||||
# stop accepting writes if it appears to be not connected with at least
|
||||
# a given number of slaves.
|
||||
# 2) Redis slaves are able to perform a partial resynchronization with the
|
||||
# a given number of replicas.
|
||||
# 2) Redis replicas are able to perform a partial resynchronization with the
|
||||
# master if the replication link is lost for a relatively small amount of
|
||||
# time. You may want to configure the replication backlog size (see the next
|
||||
# sections of this file) with a sensible value depending on your needs.
|
||||
# 3) Replication is automatic and does not need user intervention. After a
|
||||
# network partition slaves automatically try to reconnect to masters
|
||||
# network partition replicas automatically try to reconnect to masters
|
||||
# and resynchronize with them.
|
||||
#
|
||||
# slaveof <masterip> <masterport>
|
||||
# replicaof <masterip> <masterport>
|
||||
|
||||
# If the master is password protected (using the "requirepass" configuration
|
||||
# directive below) it is possible to tell the slave to authenticate before
|
||||
# directive below) it is possible to tell the replica to authenticate before
|
||||
# starting the replication synchronization process, otherwise the master will
|
||||
# refuse the slave request.
|
||||
# refuse the replica request.
|
||||
#
|
||||
# masterauth <master-password>
|
||||
|
||||
# When a slave loses its connection with the master, or when the replication
|
||||
# is still in progress, the slave can act in two different ways:
|
||||
#
|
||||
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
|
||||
# However this is not enough if you are using Redis ACLs (for Redis version
|
||||
# 6 or greater), and the default user is not capable of running the PSYNC
|
||||
# command and/or other commands needed for replication. In this case it's
|
||||
# better to configure a special user to use with replication, and specify the
|
||||
# masteruser configuration as such:
|
||||
#
|
||||
# masteruser <username>
|
||||
#
|
||||
# When masteruser is specified, the replica will authenticate against its
|
||||
# master using the new AUTH form: AUTH <username> <password>.
|
||||
|
||||
# When a replica loses its connection with the master, or when the replication
|
||||
# is still in progress, the replica can act in two different ways:
|
||||
#
|
||||
# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will
|
||||
# still reply to client requests, possibly with out of date data, or the
|
||||
# data set may just be empty if this is the first synchronization.
|
||||
#
|
||||
# 2) if slave-serve-stale-data is set to 'no' the slave will reply with
|
||||
# 2) if replica-serve-stale-data is set to 'no' the replica will reply with
|
||||
# an error "SYNC with master in progress" to all the kind of commands
|
||||
# but to INFO, SLAVEOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
|
||||
# but to INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
|
||||
# SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,
|
||||
# COMMAND, POST, HOST: and LATENCY.
|
||||
#
|
||||
slave-serve-stale-data yes
|
||||
replica-serve-stale-data yes
|
||||
|
||||
# You can configure a slave instance to accept writes or not. Writing against
|
||||
# a slave instance may be useful to store some ephemeral data (because data
|
||||
# written on a slave will be easily deleted after resync with the master) but
|
||||
# You can configure a replica instance to accept writes or not. Writing against
|
||||
# a replica instance may be useful to store some ephemeral data (because data
|
||||
# written on a replica will be easily deleted after resync with the master) but
|
||||
# may also cause problems if clients are writing to it because of a
|
||||
# misconfiguration.
|
||||
#
|
||||
# Since Redis 2.6 by default slaves are read-only.
|
||||
# Since Redis 2.6 by default replicas are read-only.
|
||||
#
|
||||
# Note: read only slaves are not designed to be exposed to untrusted clients
|
||||
# Note: read only replicas are not designed to be exposed to untrusted clients
|
||||
# on the internet. It's just a protection layer against misuse of the instance.
|
||||
# Still a read only slave exports by default all the administrative commands
|
||||
# Still a read only replica exports by default all the administrative commands
|
||||
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
|
||||
# security of read only slaves using 'rename-command' to shadow all the
|
||||
# security of read only replicas using 'rename-command' to shadow all the
|
||||
# administrative / dangerous commands.
|
||||
slave-read-only yes
|
||||
replica-read-only yes
|
||||
|
||||
# Replication SYNC strategy: disk or socket.
|
||||
#
|
||||
# -------------------------------------------------------
|
||||
# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY
|
||||
# -------------------------------------------------------
|
||||
# New replicas and reconnecting replicas that are not able to continue the
|
||||
# replication process just receiving differences, need to do what is called a
|
||||
# "full synchronization". An RDB file is transmitted from the master to the
|
||||
# replicas.
|
||||
#
|
||||
# New slaves and reconnecting slaves that are not able to continue the replication
|
||||
# process just receiving differences, need to do what is called a "full
|
||||
# synchronization". An RDB file is transmitted from the master to the slaves.
|
||||
# The transmission can happen in two different ways:
|
||||
#
|
||||
# 1) Disk-backed: The Redis master creates a new process that writes the RDB
|
||||
# file on disk. Later the file is transferred by the parent
|
||||
# process to the slaves incrementally.
|
||||
# process to the replicas incrementally.
|
||||
# 2) Diskless: The Redis master creates a new process that directly writes the
|
||||
# RDB file to slave sockets, without touching the disk at all.
|
||||
# RDB file to replica sockets, without touching the disk at all.
|
||||
#
|
||||
# With disk-backed replication, while the RDB file is generated, more slaves
|
||||
# can be queued and served with the RDB file as soon as the current child producing
|
||||
# the RDB file finishes its work. With diskless replication instead once
|
||||
# the transfer starts, new slaves arriving will be queued and a new transfer
|
||||
# will start when the current one terminates.
|
||||
# With disk-backed replication, while the RDB file is generated, more replicas
|
||||
# can be queued and served with the RDB file as soon as the current child
|
||||
# producing the RDB file finishes its work. With diskless replication instead
|
||||
# once the transfer starts, new replicas arriving will be queued and a new
|
||||
# transfer will start when the current one terminates.
|
||||
#
|
||||
# When diskless replication is used, the master waits a configurable amount of
|
||||
# time (in seconds) before starting the transfer in the hope that multiple slaves
|
||||
# will arrive and the transfer can be parallelized.
|
||||
# time (in seconds) before starting the transfer in the hope that multiple
|
||||
# replicas will arrive and the transfer can be parallelized.
|
||||
#
|
||||
# With slow disks and fast (large bandwidth) networks, diskless replication
|
||||
# works better.
|
||||
@ -351,157 +365,323 @@ repl-diskless-sync no
|
||||
|
||||
# When diskless replication is enabled, it is possible to configure the delay
|
||||
# the server waits in order to spawn the child that transfers the RDB via socket
|
||||
# to the slaves.
|
||||
# to the replicas.
|
||||
#
|
||||
# This is important since once the transfer starts, it is not possible to serve
|
||||
# new slaves arriving, that will be queued for the next RDB transfer, so the server
|
||||
# waits a delay in order to let more slaves arrive.
|
||||
# new replicas arriving, that will be queued for the next RDB transfer, so the
|
||||
# server waits a delay in order to let more replicas arrive.
|
||||
#
|
||||
# The delay is specified in seconds, and by default is 5 seconds. To disable
|
||||
# it entirely just set it to 0 seconds and the transfer will start ASAP.
|
||||
repl-diskless-sync-delay 5
|
||||
|
||||
# Slaves send PINGs to server in a predefined interval. It's possible to change
|
||||
# this interval with the repl_ping_slave_period option. The default value is 10
|
||||
# seconds.
|
||||
# -----------------------------------------------------------------------------
|
||||
# WARNING: RDB diskless load is experimental. Since in this setup the replica
|
||||
# does not immediately store an RDB on disk, it may cause data loss during
|
||||
# failovers. RDB diskless load + Redis modules not handling I/O reads may also
|
||||
# cause Redis to abort in case of I/O errors during the initial synchronization
|
||||
# stage with the master. Use only if your do what you are doing.
|
||||
# -----------------------------------------------------------------------------
|
||||
#
|
||||
# repl-ping-slave-period 10
|
||||
# Replica can load the RDB it reads from the replication link directly from the
|
||||
# socket, or store the RDB to a file and read that file after it was completely
|
||||
# recived from the master.
|
||||
#
|
||||
# In many cases the disk is slower than the network, and storing and loading
|
||||
# the RDB file may increase replication time (and even increase the master's
|
||||
# Copy on Write memory and salve buffers).
|
||||
# However, parsing the RDB file directly from the socket may mean that we have
|
||||
# to flush the contents of the current database before the full rdb was
|
||||
# received. For this reason we have the following options:
|
||||
#
|
||||
# "disabled" - Don't use diskless load (store the rdb file to the disk first)
|
||||
# "on-empty-db" - Use diskless load only when it is completely safe.
|
||||
# "swapdb" - Keep a copy of the current db contents in RAM while parsing
|
||||
# the data directly from the socket. note that this requires
|
||||
# sufficient memory, if you don't have it, you risk an OOM kill.
|
||||
repl-diskless-load disabled
|
||||
|
||||
# Replicas send PINGs to server in a predefined interval. It's possible to
|
||||
# change this interval with the repl_ping_replica_period option. The default
|
||||
# value is 10 seconds.
|
||||
#
|
||||
# repl-ping-replica-period 10
|
||||
|
||||
# The following option sets the replication timeout for:
|
||||
#
|
||||
# 1) Bulk transfer I/O during SYNC, from the point of view of slave.
|
||||
# 2) Master timeout from the point of view of slaves (data, pings).
|
||||
# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings).
|
||||
# 1) Bulk transfer I/O during SYNC, from the point of view of replica.
|
||||
# 2) Master timeout from the point of view of replicas (data, pings).
|
||||
# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).
|
||||
#
|
||||
# It is important to make sure that this value is greater than the value
|
||||
# specified for repl-ping-slave-period otherwise a timeout will be detected
|
||||
# every time there is low traffic between the master and the slave.
|
||||
# specified for repl-ping-replica-period otherwise a timeout will be detected
|
||||
# every time there is low traffic between the master and the replica.
|
||||
#
|
||||
# repl-timeout 60
|
||||
|
||||
# Disable TCP_NODELAY on the slave socket after SYNC?
|
||||
# Disable TCP_NODELAY on the replica socket after SYNC?
|
||||
#
|
||||
# If you select "yes" Redis will use a smaller number of TCP packets and
|
||||
# less bandwidth to send data to slaves. But this can add a delay for
|
||||
# the data to appear on the slave side, up to 40 milliseconds with
|
||||
# less bandwidth to send data to replicas. But this can add a delay for
|
||||
# the data to appear on the replica side, up to 40 milliseconds with
|
||||
# Linux kernels using a default configuration.
|
||||
#
|
||||
# If you select "no" the delay for data to appear on the slave side will
|
||||
# If you select "no" the delay for data to appear on the replica side will
|
||||
# be reduced but more bandwidth will be used for replication.
|
||||
#
|
||||
# By default we optimize for low latency, but in very high traffic conditions
|
||||
# or when the master and slaves are many hops away, turning this to "yes" may
|
||||
# or when the master and replicas are many hops away, turning this to "yes" may
|
||||
# be a good idea.
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# Set the replication backlog size. The backlog is a buffer that accumulates
|
||||
# slave data when slaves are disconnected for some time, so that when a slave
|
||||
# wants to reconnect again, often a full resync is not needed, but a partial
|
||||
# resync is enough, just passing the portion of data the slave missed while
|
||||
# disconnected.
|
||||
# replica data when replicas are disconnected for some time, so that when a
|
||||
# replica wants to reconnect again, often a full resync is not needed, but a
|
||||
# partial resync is enough, just passing the portion of data the replica
|
||||
# missed while disconnected.
|
||||
#
|
||||
# The bigger the replication backlog, the longer the time the slave can be
|
||||
# The bigger the replication backlog, the longer the time the replica can be
|
||||
# disconnected and later be able to perform a partial resynchronization.
|
||||
#
|
||||
# The backlog is only allocated once there is at least a slave connected.
|
||||
# The backlog is only allocated once there is at least a replica connected.
|
||||
#
|
||||
# repl-backlog-size 1mb
|
||||
|
||||
# After a master has no longer connected slaves for some time, the backlog
|
||||
# After a master has no longer connected replicas for some time, the backlog
|
||||
# will be freed. The following option configures the amount of seconds that
|
||||
# need to elapse, starting from the time the last slave disconnected, for
|
||||
# need to elapse, starting from the time the last replica disconnected, for
|
||||
# the backlog buffer to be freed.
|
||||
#
|
||||
# Note that slaves never free the backlog for timeout, since they may be
|
||||
# Note that replicas never free the backlog for timeout, since they may be
|
||||
# promoted to masters later, and should be able to correctly "partially
|
||||
# resynchronize" with the slaves: hence they should always accumulate backlog.
|
||||
# resynchronize" with the replicas: hence they should always accumulate backlog.
|
||||
#
|
||||
# A value of 0 means to never release the backlog.
|
||||
#
|
||||
# repl-backlog-ttl 3600
|
||||
|
||||
# The slave priority is an integer number published by Redis in the INFO output.
|
||||
# It is used by Redis Sentinel in order to select a slave to promote into a
|
||||
# master if the master is no longer working correctly.
|
||||
# The replica priority is an integer number published by Redis in the INFO
|
||||
# output. It is used by Redis Sentinel in order to select a replica to promote
|
||||
# into a master if the master is no longer working correctly.
|
||||
#
|
||||
# A slave with a low priority number is considered better for promotion, so
|
||||
# for instance if there are three slaves with priority 10, 100, 25 Sentinel will
|
||||
# pick the one with priority 10, that is the lowest.
|
||||
# A replica with a low priority number is considered better for promotion, so
|
||||
# for instance if there are three replicas with priority 10, 100, 25 Sentinel
|
||||
# will pick the one with priority 10, that is the lowest.
|
||||
#
|
||||
# However a special priority of 0 marks the slave as not able to perform the
|
||||
# role of master, so a slave with priority of 0 will never be selected by
|
||||
# However a special priority of 0 marks the replica as not able to perform the
|
||||
# role of master, so a replica with priority of 0 will never be selected by
|
||||
# Redis Sentinel for promotion.
|
||||
#
|
||||
# By default the priority is 100.
|
||||
slave-priority 100
|
||||
replica-priority 100
|
||||
|
||||
# It is possible for a master to stop accepting writes if there are less than
|
||||
# N slaves connected, having a lag less or equal than M seconds.
|
||||
# N replicas connected, having a lag less or equal than M seconds.
|
||||
#
|
||||
# The N slaves need to be in "online" state.
|
||||
# The N replicas need to be in "online" state.
|
||||
#
|
||||
# The lag in seconds, that must be <= the specified value, is calculated from
|
||||
# the last ping received from the slave, that is usually sent every second.
|
||||
# the last ping received from the replica, that is usually sent every second.
|
||||
#
|
||||
# This option does not GUARANTEE that N replicas will accept the write, but
|
||||
# will limit the window of exposure for lost writes in case not enough slaves
|
||||
# will limit the window of exposure for lost writes in case not enough replicas
|
||||
# are available, to the specified number of seconds.
|
||||
#
|
||||
# For example to require at least 3 slaves with a lag <= 10 seconds use:
|
||||
# For example to require at least 3 replicas with a lag <= 10 seconds use:
|
||||
#
|
||||
# min-slaves-to-write 3
|
||||
# min-slaves-max-lag 10
|
||||
# min-replicas-to-write 3
|
||||
# min-replicas-max-lag 10
|
||||
#
|
||||
# Setting one or the other to 0 disables the feature.
|
||||
#
|
||||
# By default min-slaves-to-write is set to 0 (feature disabled) and
|
||||
# min-slaves-max-lag is set to 10.
|
||||
# By default min-replicas-to-write is set to 0 (feature disabled) and
|
||||
# min-replicas-max-lag is set to 10.
|
||||
|
||||
# A Redis master is able to list the address and port of the attached
|
||||
# slaves in different ways. For example the "INFO replication" section
|
||||
# replicas in different ways. For example the "INFO replication" section
|
||||
# offers this information, which is used, among other tools, by
|
||||
# Redis Sentinel in order to discover slave instances.
|
||||
# Redis Sentinel in order to discover replica instances.
|
||||
# Another place where this info is available is in the output of the
|
||||
# "ROLE" command of a master.
|
||||
#
|
||||
# The listed IP and address normally reported by a slave is obtained
|
||||
# The listed IP and address normally reported by a replica is obtained
|
||||
# in the following way:
|
||||
#
|
||||
# IP: The address is auto detected by checking the peer address
|
||||
# of the socket used by the slave to connect with the master.
|
||||
# of the socket used by the replica to connect with the master.
|
||||
#
|
||||
# Port: The port is communicated by the slave during the replication
|
||||
# handshake, and is normally the port that the slave is using to
|
||||
# list for connections.
|
||||
# Port: The port is communicated by the replica during the replication
|
||||
# handshake, and is normally the port that the replica is using to
|
||||
# listen for connections.
|
||||
#
|
||||
# However when port forwarding or Network Address Translation (NAT) is
|
||||
# used, the slave may be actually reachable via different IP and port
|
||||
# pairs. The following two options can be used by a slave in order to
|
||||
# used, the replica may be actually reachable via different IP and port
|
||||
# pairs. The following two options can be used by a replica in order to
|
||||
# report to its master a specific set of IP and port, so that both INFO
|
||||
# and ROLE will report those values.
|
||||
#
|
||||
# There is no need to use both the options if you need to override just
|
||||
# the port or the IP address.
|
||||
#
|
||||
# slave-announce-ip 5.5.5.5
|
||||
# slave-announce-port 1234
|
||||
# replica-announce-ip 5.5.5.5
|
||||
# replica-announce-port 1234
|
||||
|
||||
############################### KEYS TRACKING #################################
|
||||
|
||||
# Redis implements server assisted support for client side caching of values.
|
||||
# This is implemented using an invalidation table that remembers, using
|
||||
# 16 millions of slots, what clients may have certain subsets of keys. In turn
|
||||
# this is used in order to send invalidation messages to clients. Please
|
||||
# to understand more about the feature check this page:
|
||||
#
|
||||
# https://redis.io/topics/client-side-caching
|
||||
#
|
||||
# When tracking is enabled for a client, all the read only queries are assumed
|
||||
# to be cached: this will force Redis to store information in the invalidation
|
||||
# table. When keys are modified, such information is flushed away, and
|
||||
# invalidation messages are sent to the clients. However if the workload is
|
||||
# heavily dominated by reads, Redis could use more and more memory in order
|
||||
# to track the keys fetched by many clients.
|
||||
#
|
||||
# For this reason it is possible to configure a maximum fill value for the
|
||||
# invalidation table. By default it is set to 10%, and once this limit is
|
||||
# reached, Redis will start to evict caching slots in the invalidation table
|
||||
# even if keys are not modified, just to reclaim memory: this will in turn
|
||||
# force the clients to invalidate the cached values. Basically the table
|
||||
# maximum fill rate is a trade off between the memory you want to spend server
|
||||
# side to track information about who cached what, and the ability of clients
|
||||
# to retain cached objects in memory.
|
||||
#
|
||||
# If you set the value to 0, it means there are no limits, and all the 16
|
||||
# millions of caching slots can be used at the same time. In the "stats"
|
||||
# INFO section, you can find information about the amount of caching slots
|
||||
# used at every given moment.
|
||||
#
|
||||
# tracking-table-max-fill 10
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||
# commands. This might be useful in environments in which you do not trust
|
||||
# others with access to the host running redis-server.
|
||||
#
|
||||
# This should stay commented out for backward compatibility and because most
|
||||
# people do not need auth (e.g. they run their own servers).
|
||||
#
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# 150k passwords per second against a good box. This means that you should
|
||||
# use a very strong password otherwise it will be very easy to break.
|
||||
# 1 million passwords per second against a modern box. This means that you
|
||||
# should use very strong passwords, otherwise they will be very easy to break.
|
||||
# Note that because the password is really a shared secret between the client
|
||||
# and the server, and should not be memorized by any human, the password
|
||||
# can be easily a long string from /dev/urandom or whatever, so by using a
|
||||
# long and unguessable password no brute force attack will be possible.
|
||||
|
||||
# Redis ACL users are defined in the following format:
|
||||
#
|
||||
# user <username> ... acl rules ...
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
|
||||
#
|
||||
# The special username "default" is used for new connections. If this user
|
||||
# has the "nopass" rule, then new connections will be immediately authenticated
|
||||
# as the "default" user without the need of any password provided via the
|
||||
# AUTH command. Otherwise if the "default" user is not flagged with "nopass"
|
||||
# the connections will start in not authenticated state, and will require
|
||||
# AUTH (or the HELLO command AUTH option) in order to be authenticated and
|
||||
# start to work.
|
||||
#
|
||||
# The ACL rules that describe what an user can do are the following:
|
||||
#
|
||||
# on Enable the user: it is possible to authenticate as this user.
|
||||
# off Disable the user: it's no longer possible to authenticate
|
||||
# with this user, however the already authenticated connections
|
||||
# will still work.
|
||||
# +<command> Allow the execution of that command
|
||||
# -<command> Disallow the execution of that command
|
||||
# +@<category> Allow the execution of all the commands in such category
|
||||
# with valid categories are like @admin, @set, @sortedset, ...
|
||||
# and so forth, see the full list in the server.c file where
|
||||
# the Redis command table is described and defined.
|
||||
# The special category @all means all the commands, but currently
|
||||
# present in the server, and that will be loaded in the future
|
||||
# via modules.
|
||||
# +<command>|subcommand Allow a specific subcommand of an otherwise
|
||||
# disabled command. Note that this form is not
|
||||
# allowed as negative like -DEBUG|SEGFAULT, but
|
||||
# only additive starting with "+".
|
||||
# allcommands Alias for +@all. Note that it implies the ability to execute
|
||||
# all the future commands loaded via the modules system.
|
||||
# nocommands Alias for -@all.
|
||||
# ~<pattern> Add a pattern of keys that can be mentioned as part of
|
||||
# commands. For instance ~* allows all the keys. The pattern
|
||||
# is a glob-style pattern like the one of KEYS.
|
||||
# It is possible to specify multiple patterns.
|
||||
# allkeys Alias for ~*
|
||||
# resetkeys Flush the list of allowed keys patterns.
|
||||
# ><password> Add this passowrd 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.
|
||||
# nopass All the set passwords of the user are removed, and the user
|
||||
# is flagged as requiring no password: it means that every
|
||||
# password will work against this user. If this directive is
|
||||
# used for the default user, every new connection will be
|
||||
# immediately authenticated with the default user without
|
||||
# any explicit AUTH command required. Note that the "resetpass"
|
||||
# directive will clear this condition.
|
||||
# resetpass Flush the list of allowed passwords. Moreover removes the
|
||||
# "nopass" status. After "resetpass" the user has no associated
|
||||
# passwords and there is no way to authenticate without adding
|
||||
# some password (or setting it as "nopass" later).
|
||||
# reset Performs the following actions: resetpass, resetkeys, off,
|
||||
# -@all. The user returns to the same state it has immediately
|
||||
# after its creation.
|
||||
#
|
||||
# ACL rules can be specified in any order: for instance you can start with
|
||||
# passwords, then flags, or key patterns. However note that the additive
|
||||
# and subtractive rules will CHANGE MEANING depending on the ordering.
|
||||
# For instance see the following example:
|
||||
#
|
||||
# user alice on +@all -DEBUG ~* >somepassword
|
||||
#
|
||||
# This will allow "alice" to use all the commands with the exception of the
|
||||
# DEBUG command, since +@all added all the commands to the set of the commands
|
||||
# alice can use, and later DEBUG was removed. However if we invert the order
|
||||
# of two ACL rules the result will be different:
|
||||
#
|
||||
# user alice on -DEBUG +@all ~* >somepassword
|
||||
#
|
||||
# Now DEBUG was removed when alice had yet no commands in the set of allowed
|
||||
# commands, later all the commands are added, so the user will be able to
|
||||
# execute everything.
|
||||
#
|
||||
# Basically ACL rules are processed left-to-right.
|
||||
#
|
||||
# For more information about ACL configuration please refer to
|
||||
# the Redis web site at https://redis.io/topics/acl
|
||||
|
||||
# Using an external ACL file
|
||||
#
|
||||
# Instead of configuring users here in this file, it is possible to use
|
||||
# a stand-alone file just listing users. The two methods cannot be mixed:
|
||||
# if you configure users here and at the same time you activate the exteranl
|
||||
# ACL file, the server will refuse to start.
|
||||
#
|
||||
# The format of the external ACL user file is exactly the same as the
|
||||
# format that is used inside redis.conf to describe users.
|
||||
#
|
||||
# aclfile /etc/redis/users.acl
|
||||
|
||||
# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
|
||||
# layer on top of the new ACL system. The option effect will be just setting
|
||||
# the password for the default user. Clients will still authenticate using
|
||||
# AUTH <password> as usually, or more explicitly with AUTH default <password>
|
||||
# if they follow the new protocol: both will work.
|
||||
#
|
||||
# requirepass foobared
|
||||
|
||||
# Command renaming.
|
||||
# Command renaming (DEPRECATED).
|
||||
#
|
||||
# ------------------------------------------------------------------------
|
||||
# WARNING: avoid using this option if possible. Instead use ACLs to remove
|
||||
# commands from the default user, and put them only in some admin user you
|
||||
# create for administrative purposes.
|
||||
# ------------------------------------------------------------------------
|
||||
#
|
||||
# It is possible to change the name of dangerous commands in a shared
|
||||
# environment. For instance the CONFIG command may be renamed into something
|
||||
@ -518,7 +698,7 @@ slave-priority 100
|
||||
# rename-command CONFIG ""
|
||||
#
|
||||
# Please note that changing the name of commands that are logged into the
|
||||
# AOF file or transmitted to slaves may cause problems.
|
||||
# AOF file or transmitted to replicas may cause problems.
|
||||
|
||||
################################### CLIENTS ####################################
|
||||
|
||||
@ -547,15 +727,15 @@ slave-priority 100
|
||||
# This option is usually useful when using Redis as an LRU or LFU cache, or to
|
||||
# set a hard memory limit for an instance (using the 'noeviction' policy).
|
||||
#
|
||||
# WARNING: If you have slaves attached to an instance with maxmemory on,
|
||||
# the size of the output buffers needed to feed the slaves are subtracted
|
||||
# WARNING: If you have replicas attached to an instance with maxmemory on,
|
||||
# the size of the output buffers needed to feed the replicas are subtracted
|
||||
# from the used memory count, so that network problems / resyncs will
|
||||
# not trigger a loop where keys are evicted, and in turn the output
|
||||
# buffer of slaves is full with DELs of keys evicted triggering the deletion
|
||||
# buffer of replicas is full with DELs of keys evicted triggering the deletion
|
||||
# of more keys, and so forth until the database is completely emptied.
|
||||
#
|
||||
# In short... if you have slaves attached it is suggested that you set a lower
|
||||
# limit for maxmemory so that there is some free RAM on the system for slave
|
||||
# In short... if you have replicas attached it is suggested that you set a lower
|
||||
# limit for maxmemory so that there is some free RAM on the system for replica
|
||||
# output buffers (but this is not needed if the policy is 'noeviction').
|
||||
#
|
||||
# maxmemory <bytes>
|
||||
@ -602,6 +782,26 @@ slave-priority 100
|
||||
#
|
||||
# maxmemory-samples 5
|
||||
|
||||
# Starting from Redis 5, by default a replica will ignore its maxmemory setting
|
||||
# (unless it is promoted to master after a failover or manually). It means
|
||||
# that the eviction of keys will be just handled by the master, sending the
|
||||
# DEL commands to the replica as keys evict in the master side.
|
||||
#
|
||||
# This behavior ensures that masters and replicas stay consistent, and is usually
|
||||
# what you want, however if your replica is writable, or you want the replica
|
||||
# to have a different memory setting, and you are sure all the writes performed
|
||||
# to the replica are idempotent, then you may change this default (but be sure
|
||||
# to understand what you are doing).
|
||||
#
|
||||
# Note that since the replica by default does not evict, it may end using more
|
||||
# memory than the one set via maxmemory (there are certain buffers that may
|
||||
# be larger on the replica, or data structures may sometimes take more memory
|
||||
# and so forth). So make sure you monitor your replicas and make sure they
|
||||
# have enough memory to never hit a real out-of-memory condition before the
|
||||
# master hits the configured maxmemory setting.
|
||||
#
|
||||
# replica-ignore-maxmemory yes
|
||||
|
||||
############################# LAZY FREEING ####################################
|
||||
|
||||
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
||||
@ -637,9 +837,9 @@ slave-priority 100
|
||||
# or SORT with STORE option may delete existing keys. The SET command
|
||||
# itself removes any old content of the specified key in order to replace
|
||||
# it with the specified string.
|
||||
# 4) During replication, when a slave performs a full resynchronization with
|
||||
# 4) During replication, when a replica performs a full resynchronization with
|
||||
# its master, the content of the whole database is removed in order to
|
||||
# load the RDB file just transfered.
|
||||
# load the RDB file just transferred.
|
||||
#
|
||||
# In all the above cases the default is to delete objects in a blocking way,
|
||||
# like if DEL was called. However you can configure each case specifically
|
||||
@ -649,7 +849,7 @@ slave-priority 100
|
||||
lazyfree-lazy-eviction no
|
||||
lazyfree-lazy-expire no
|
||||
lazyfree-lazy-server-del no
|
||||
slave-lazy-flush no
|
||||
replica-lazy-flush no
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
@ -799,13 +999,7 @@ aof-use-rdb-preamble yes
|
||||
lua-time-limit 5000
|
||||
|
||||
################################ REDIS CLUSTER ###############################
|
||||
#
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# WARNING EXPERIMENTAL: Redis Cluster is considered to be stable code, however
|
||||
# in order to mark it as "mature" we need to wait for a non trivial percentage
|
||||
# of users to deploy it in production.
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
#
|
||||
|
||||
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
|
||||
# started as cluster nodes can. In order to start a Redis instance as a
|
||||
# cluster node enable the cluster support uncommenting the following:
|
||||
@ -826,42 +1020,42 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-node-timeout 15000
|
||||
|
||||
# A slave of a failing master will avoid to start a failover if its data
|
||||
# A replica of a failing master will avoid to start a failover if its data
|
||||
# looks too old.
|
||||
#
|
||||
# There is no simple way for a slave to actually have an exact measure of
|
||||
# There is no simple way for a replica to actually have an exact measure of
|
||||
# its "data age", so the following two checks are performed:
|
||||
#
|
||||
# 1) If there are multiple slaves able to failover, they exchange messages
|
||||
# in order to try to give an advantage to the slave with the best
|
||||
# 1) If there are multiple replicas able to failover, they exchange messages
|
||||
# in order to try to give an advantage to the replica with the best
|
||||
# replication offset (more data from the master processed).
|
||||
# Slaves will try to get their rank by offset, and apply to the start
|
||||
# Replicas will try to get their rank by offset, and apply to the start
|
||||
# of the failover a delay proportional to their rank.
|
||||
#
|
||||
# 2) Every single slave computes the time of the last interaction with
|
||||
# 2) Every single replica computes the time of the last interaction with
|
||||
# its master. This can be the last ping or command received (if the master
|
||||
# is still in the "connected" state), or the time that elapsed since the
|
||||
# disconnection with the master (if the replication link is currently down).
|
||||
# If the last interaction is too old, the slave will not try to failover
|
||||
# If the last interaction is too old, the replica will not try to failover
|
||||
# at all.
|
||||
#
|
||||
# The point "2" can be tuned by user. Specifically a slave will not perform
|
||||
# The point "2" can be tuned by user. Specifically a replica will not perform
|
||||
# the failover if, since the last interaction with the master, the time
|
||||
# elapsed is greater than:
|
||||
#
|
||||
# (node-timeout * slave-validity-factor) + repl-ping-slave-period
|
||||
# (node-timeout * replica-validity-factor) + repl-ping-replica-period
|
||||
#
|
||||
# So for example if node-timeout is 30 seconds, and the slave-validity-factor
|
||||
# is 10, and assuming a default repl-ping-slave-period of 10 seconds, the
|
||||
# slave will not try to failover if it was not able to talk with the master
|
||||
# So for example if node-timeout is 30 seconds, and the replica-validity-factor
|
||||
# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the
|
||||
# replica will not try to failover if it was not able to talk with the master
|
||||
# for longer than 310 seconds.
|
||||
#
|
||||
# A large slave-validity-factor may allow slaves with too old data to failover
|
||||
# A large replica-validity-factor may allow replicas with too old data to failover
|
||||
# a master, while a too small value may prevent the cluster from being able to
|
||||
# elect a slave at all.
|
||||
# elect a replica at all.
|
||||
#
|
||||
# For maximum availability, it is possible to set the slave-validity-factor
|
||||
# to a value of 0, which means, that slaves will always try to failover the
|
||||
# For maximum availability, it is possible to set the replica-validity-factor
|
||||
# to a value of 0, which means, that replicas will always try to failover the
|
||||
# master regardless of the last time they interacted with the master.
|
||||
# (However they'll always try to apply a delay proportional to their
|
||||
# offset rank).
|
||||
@ -869,22 +1063,22 @@ lua-time-limit 5000
|
||||
# Zero is the only value able to guarantee that when all the partitions heal
|
||||
# the cluster will always be able to continue.
|
||||
#
|
||||
# cluster-slave-validity-factor 10
|
||||
# cluster-replica-validity-factor 10
|
||||
|
||||
# Cluster slaves are able to migrate to orphaned masters, that are masters
|
||||
# that are left without working slaves. This improves the cluster ability
|
||||
# Cluster replicas are able to migrate to orphaned masters, that are masters
|
||||
# that are left without working replicas. This improves the cluster ability
|
||||
# to resist to failures as otherwise an orphaned master can't be failed over
|
||||
# in case of failure if it has no working slaves.
|
||||
# in case of failure if it has no working replicas.
|
||||
#
|
||||
# Slaves migrate to orphaned masters only if there are still at least a
|
||||
# given number of other working slaves for their old master. This number
|
||||
# is the "migration barrier". A migration barrier of 1 means that a slave
|
||||
# will migrate only if there is at least 1 other working slave for its master
|
||||
# and so forth. It usually reflects the number of slaves you want for every
|
||||
# Replicas migrate to orphaned masters only if there are still at least a
|
||||
# given number of other working replicas for their old master. This number
|
||||
# is the "migration barrier". A migration barrier of 1 means that a replica
|
||||
# will migrate only if there is at least 1 other working replica for its master
|
||||
# and so forth. It usually reflects the number of replicas you want for every
|
||||
# master in your cluster.
|
||||
#
|
||||
# Default is 1 (slaves migrate only if their masters remain with at least
|
||||
# one slave). To disable migration just set it to a very large value.
|
||||
# Default is 1 (replicas migrate only if their masters remain with at least
|
||||
# one replica). To disable migration just set it to a very large value.
|
||||
# A value of 0 can be set but is useful only for debugging and dangerous
|
||||
# in production.
|
||||
#
|
||||
@ -903,7 +1097,7 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-require-full-coverage yes
|
||||
|
||||
# This option, when set to yes, prevents slaves from trying to failover its
|
||||
# This option, when set to yes, prevents replicas from trying to failover its
|
||||
# master during master failures. However the master can still perform a
|
||||
# manual failover, if forced to do so.
|
||||
#
|
||||
@ -911,7 +1105,7 @@ lua-time-limit 5000
|
||||
# data center operations, where we want one side to never be promoted if not
|
||||
# in the case of a total DC failure.
|
||||
#
|
||||
# cluster-slave-no-failover no
|
||||
# cluster-replica-no-failover no
|
||||
|
||||
# In order to setup your cluster make sure to read the documentation
|
||||
# available at http://redis.io web site.
|
||||
@ -1040,6 +1234,61 @@ latency-monitor-threshold 0
|
||||
# specify at least one of K or E, no events will be delivered.
|
||||
notify-keyspace-events ""
|
||||
|
||||
############################### GOPHER SERVER #################################
|
||||
|
||||
# Redis contains an implementation of the Gopher protocol, as specified in
|
||||
# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
|
||||
#
|
||||
# The Gopher protocol was very popular in the late '90s. It is an alternative
|
||||
# to the web, and the implementation both server and client side is so simple
|
||||
# that the Redis server has just 100 lines of code in order to implement this
|
||||
# support.
|
||||
#
|
||||
# What do you do with Gopher nowadays? Well Gopher never *really* died, and
|
||||
# lately there is a movement in order for the Gopher more hierarchical content
|
||||
# composed of just plain text documents to be resurrected. Some want a simpler
|
||||
# internet, others believe that the mainstream internet became too much
|
||||
# controlled, and it's cool to create an alternative space for people that
|
||||
# want a bit of fresh air.
|
||||
#
|
||||
# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol
|
||||
# as a gift.
|
||||
#
|
||||
# --- HOW IT WORKS? ---
|
||||
#
|
||||
# The Redis Gopher support uses the inline protocol of Redis, and specifically
|
||||
# two kind of inline requests that were anyway illegal: an empty request
|
||||
# or any request that starts with "/" (there are no Redis commands starting
|
||||
# with such a slash). Normal RESP2/RESP3 requests are completely out of the
|
||||
# path of the Gopher protocol implementation and are served as usually as well.
|
||||
#
|
||||
# If you open a connection to Redis when Gopher is enabled and send it
|
||||
# a string like "/foo", if there is a key named "/foo" it is served via the
|
||||
# Gopher protocol.
|
||||
#
|
||||
# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher
|
||||
# talking), you likely need a script like the following:
|
||||
#
|
||||
# https://github.com/antirez/gopher2redis
|
||||
#
|
||||
# --- SECURITY WARNING ---
|
||||
#
|
||||
# If you plan to put Redis on the internet in a publicly accessible address
|
||||
# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
|
||||
# Once a password is set:
|
||||
#
|
||||
# 1. The Gopher server (when enabled, not by default) will kill serve
|
||||
# content via Gopher.
|
||||
# 2. However other commands cannot be called before the client will
|
||||
# authenticate.
|
||||
#
|
||||
# So use the 'requirepass' option to protect your instance.
|
||||
#
|
||||
# To enable Gopher support uncomment the following line and set
|
||||
# the option from no (the default) to yes.
|
||||
#
|
||||
# gopher-enabled no
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
# Hashes are encoded using a memory efficient data structure when they have a
|
||||
@ -1145,7 +1394,7 @@ activerehashing yes
|
||||
# The limit can be set differently for the three different classes of clients:
|
||||
#
|
||||
# normal -> normal clients including MONITOR clients
|
||||
# slave -> slave clients
|
||||
# replica -> replica clients
|
||||
# pubsub -> clients subscribed to at least one pubsub channel or pattern
|
||||
#
|
||||
# The syntax of every client-output-buffer-limit directive is the following:
|
||||
@ -1166,12 +1415,12 @@ activerehashing yes
|
||||
# asynchronous clients may create a scenario where data is requested faster
|
||||
# than it can read.
|
||||
#
|
||||
# Instead there is a default limit for pubsub and slave clients, since
|
||||
# subscribers and slaves receive data in a push fashion.
|
||||
# Instead there is a default limit for pubsub and replica clients, since
|
||||
# subscribers and replicas receive data in a push fashion.
|
||||
#
|
||||
# Both the hard or the soft limit can be disabled by setting them to zero.
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit replica 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# Client query buffers accumulate new commands. They are limited to a fixed
|
||||
@ -1205,6 +1454,22 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
# 100 only in environments where very low latency is required.
|
||||
hz 10
|
||||
|
||||
# Normally it is useful to have an HZ value which is proportional to the
|
||||
# number of clients connected. This is useful in order, for instance, to
|
||||
# avoid too many clients are processed for each background task invocation
|
||||
# in order to avoid latency spikes.
|
||||
#
|
||||
# Since the default HZ value by default is conservatively set to 10, Redis
|
||||
# offers, and enables by default, the ability to use an adaptive HZ value
|
||||
# which will temporary raise when there are many connected clients.
|
||||
#
|
||||
# When dynamic HZ is enabled, the actual configured HZ will be used as
|
||||
# as a baseline, but multiples of the configured HZ value will be actually
|
||||
# used as needed once more clients are connected. In this way an idle
|
||||
# instance will use very little CPU time while a busy instance will be
|
||||
# more responsive.
|
||||
dynamic-hz yes
|
||||
|
||||
# When a child rewrites the AOF file, if the following option is enabled
|
||||
# the file will be fsync-ed every 32 MB of data generated. This is useful
|
||||
# in order to commit the file to the disk more incrementally and avoid
|
||||
|
2
runtest
2
runtest
@ -11,4 +11,4 @@ then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/test_helper.tcl $*
|
||||
$TCLSH tests/test_helper.tcl "${@}"
|
||||
|
16
runtest-moduleapi
Executable file
16
runtest-moduleapi
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis test"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
|
@ -20,6 +20,21 @@
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis-sentinel.pid when
|
||||
# daemonized.
|
||||
daemonize no
|
||||
|
||||
# When running daemonized, Redis Sentinel writes a pid file in
|
||||
# /var/run/redis-sentinel.pid by default. You can specify a custom pid file
|
||||
# location here.
|
||||
pidfile /var/run/redis-sentinel.pid
|
||||
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
# Sentinel to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile ""
|
||||
|
||||
# sentinel announce-ip <ip>
|
||||
# sentinel announce-port <port>
|
||||
#
|
||||
@ -58,11 +73,11 @@ dir /tmp
|
||||
# be elected by the majority of the known Sentinels in order to
|
||||
# start a failover, so no failover can be performed in minority.
|
||||
#
|
||||
# Slaves are auto-discovered, so you don't need to specify slaves in
|
||||
# Replicas are auto-discovered, so you don't need to specify replicas in
|
||||
# any way. Sentinel itself will rewrite this configuration file adding
|
||||
# the slaves using additional configuration options.
|
||||
# the replicas using additional configuration options.
|
||||
# Also note that the configuration file is rewritten when a
|
||||
# slave is promoted to master.
|
||||
# replica is promoted to master.
|
||||
#
|
||||
# Note: master name should not include special characters or spaces.
|
||||
# The valid charset is A-z 0-9 and the three characters ".-_".
|
||||
@ -70,11 +85,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
|
||||
# sentinel auth-pass <master-name> <password>
|
||||
#
|
||||
# Set the password to use to authenticate with the master and slaves.
|
||||
# Set the password to use to authenticate with the master and replicas.
|
||||
# Useful if there is a password set in the Redis instances to monitor.
|
||||
#
|
||||
# Note that the master password is also used for slaves, so it is not
|
||||
# possible to set a different password in masters and slaves instances
|
||||
# Note that the master password is also used for replicas, so it is not
|
||||
# possible to set a different password in masters and replicas instances
|
||||
# if you want to be able to monitor these instances with Sentinel.
|
||||
#
|
||||
# However you can have Redis instances without the authentication enabled
|
||||
@ -89,7 +104,7 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
|
||||
# sentinel down-after-milliseconds <master-name> <milliseconds>
|
||||
#
|
||||
# Number of milliseconds the master (or any attached slave or sentinel) should
|
||||
# Number of milliseconds the master (or any attached replica or sentinel) should
|
||||
# be unreachable (as in, not acceptable reply to PING, continuously, for the
|
||||
# specified period) in order to consider it in S_DOWN state (Subjectively
|
||||
# Down).
|
||||
@ -97,11 +112,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
# Default is 30 seconds.
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# sentinel parallel-syncs <master-name> <numslaves>
|
||||
# sentinel parallel-syncs <master-name> <numreplicas>
|
||||
#
|
||||
# How many slaves we can reconfigure to point to the new slave simultaneously
|
||||
# during the failover. Use a low number if you use the slaves to serve query
|
||||
# to avoid that all the slaves will be unreachable at about the same
|
||||
# How many replicas we can reconfigure to point to the new replica simultaneously
|
||||
# during the failover. Use a low number if you use the replicas to serve query
|
||||
# to avoid that all the replicas will be unreachable at about the same
|
||||
# time while performing the synchronization with the master.
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
@ -113,18 +128,18 @@ sentinel parallel-syncs mymaster 1
|
||||
# already tried against the same master by a given Sentinel, is two
|
||||
# times the failover timeout.
|
||||
#
|
||||
# - The time needed for a slave replicating to a wrong master according
|
||||
# - The time needed for a replica replicating to a wrong master according
|
||||
# to a Sentinel current configuration, to be forced to replicate
|
||||
# with the right master, is exactly the failover timeout (counting since
|
||||
# the moment a Sentinel detected the misconfiguration).
|
||||
#
|
||||
# - The time needed to cancel a failover that is already in progress but
|
||||
# did not produced any configuration change (SLAVEOF NO ONE yet not
|
||||
# acknowledged by the promoted slave).
|
||||
# acknowledged by the promoted replica).
|
||||
#
|
||||
# - The maximum time a failover in progress waits for all the slaves to be
|
||||
# reconfigured as slaves of the new master. However even after this time
|
||||
# the slaves will be reconfigured by the Sentinels anyway, but not with
|
||||
# - The maximum time a failover in progress waits for all the replicas to be
|
||||
# reconfigured as replicas of the new master. However even after this time
|
||||
# the replicas will be reconfigured by the Sentinels anyway, but not with
|
||||
# the exact parallel-syncs progression as specified.
|
||||
#
|
||||
# Default is 3 minutes.
|
||||
@ -185,7 +200,7 @@ sentinel failover-timeout mymaster 180000
|
||||
# <role> is either "leader" or "observer"
|
||||
#
|
||||
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
|
||||
# the old address of the master and the new address of the elected slave
|
||||
# the old address of the master and the new address of the elected replica
|
||||
# (now a master).
|
||||
#
|
||||
# This script should be resistant to multiple invocations.
|
||||
@ -203,3 +218,27 @@ sentinel failover-timeout mymaster 180000
|
||||
|
||||
sentinel deny-scripts-reconfig yes
|
||||
|
||||
# REDIS COMMANDS RENAMING
|
||||
#
|
||||
# Sometimes the Redis server has certain commands, that are needed for Sentinel
|
||||
# to work correctly, renamed to unguessable strings. This is often the case
|
||||
# of CONFIG and SLAVEOF in the context of providers that provide Redis as
|
||||
# a service, and don't want the customers to reconfigure the instances outside
|
||||
# of the administration console.
|
||||
#
|
||||
# In such case it is possible to tell Sentinel to use different command names
|
||||
# instead of the normal ones. For example if the master "mymaster", and the
|
||||
# associated replicas, have "CONFIG" all renamed to "GUESSME", I could use:
|
||||
#
|
||||
# SENTINEL rename-command mymaster CONFIG GUESSME
|
||||
#
|
||||
# After such configuration is set, every time Sentinel would use CONFIG it will
|
||||
# use GUESSME instead. Note that there is no actual need to respect the command
|
||||
# case, so writing "config guessme" is the same in the example above.
|
||||
#
|
||||
# SENTINEL SET can also be used in order to perform this configuration at runtime.
|
||||
#
|
||||
# In order to set a command back to its original name (undo the renaming), it
|
||||
# is possible to just rename a command to itsef:
|
||||
#
|
||||
# SENTINEL rename-command mymaster CONFIG CONFIG
|
||||
|
31
src/Makefile
31
src/Makefile
@ -20,7 +20,12 @@ DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
STD=-std=c99 -pedantic -DREDIS_STATIC=''
|
||||
STD=-std=c11 -pedantic -DREDIS_STATIC=''
|
||||
ifneq (,$(findstring clang,$(CC)))
|
||||
ifneq (,$(findstring FreeBSD,$(uname_S)))
|
||||
STD+=-Wno-c11-extensions
|
||||
endif
|
||||
endif
|
||||
WARN=-Wall -W -Wno-missing-field-initializers
|
||||
OPT=$(OPTIMIZATION)
|
||||
|
||||
@ -41,6 +46,10 @@ endif
|
||||
# To get ARM stack traces if Redis crashes we need a special C flag.
|
||||
ifneq (,$(filter aarch64 armv,$(uname_M)))
|
||||
CFLAGS+=-funwind-tables
|
||||
else
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
CFLAGS+=-funwind-tables
|
||||
endif
|
||||
endif
|
||||
|
||||
# Backwards compatibility for selecting an allocator
|
||||
@ -93,10 +102,20 @@ else
|
||||
ifeq ($(uname_S),OpenBSD)
|
||||
# OpenBSD
|
||||
FINAL_LIBS+= -lpthread
|
||||
ifeq ($(USE_BACKTRACE),yes)
|
||||
FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
|
||||
FINAL_LDFLAGS+= -L/usr/local/lib
|
||||
FINAL_LIBS+= -lexecinfo
|
||||
endif
|
||||
|
||||
else
|
||||
ifeq ($(uname_S),FreeBSD)
|
||||
# FreeBSD
|
||||
FINAL_LIBS+= -lpthread
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),DragonFly)
|
||||
# FreeBSD
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
@ -106,6 +125,7 @@ endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
|
||||
@ -144,11 +164,11 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-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
|
||||
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 lolwut.o lolwut5.o acl.o gopher.o tracking.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o zmalloc.o redis-benchmark.o
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o
|
||||
REDIS_CHECK_RDB_NAME=redis-check-rdb
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof
|
||||
|
||||
@ -290,3 +310,6 @@ install: all
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||
|
||||
uninstall:
|
||||
rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)}
|
||||
|
6
src/ae.c
6
src/ae.c
@ -351,8 +351,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
* if flags has AE_FILE_EVENTS set, file events are processed.
|
||||
* if flags has AE_TIME_EVENTS set, time events are processed.
|
||||
* if flags has AE_DONT_WAIT set the function returns ASAP until all
|
||||
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
|
||||
* the events that's possible to process without to wait are processed.
|
||||
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
|
||||
*
|
||||
* The function returns the number of events processed. */
|
||||
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
@ -433,7 +433,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
* before replying to a client. */
|
||||
int invert = fe->mask & AE_BARRIER;
|
||||
|
||||
/* Note the "fe->mask & mask & ..." code: maybe an already
|
||||
/* Note the "fe->mask & mask & ..." code: maybe an already
|
||||
* processed event removed an element that fired and we still
|
||||
* didn't processed, so we check if the event is still valid.
|
||||
*
|
||||
@ -485,7 +485,7 @@ int aeWait(int fd, int mask, long long milliseconds) {
|
||||
if ((retval = poll(&pfd, 1, milliseconds))== 1) {
|
||||
if (pfd.revents & POLLIN) retmask |= AE_READABLE;
|
||||
if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
|
||||
if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE;
|
||||
return retmask;
|
||||
} else {
|
||||
|
14
src/anet.c
14
src/anet.c
@ -193,6 +193,20 @@ int anetSendTimeout(char *err, int fd, long long ms) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* Set the socket receive timeout (SO_RCVTIMEO socket option) to the specified
|
||||
* number of milliseconds, or disable it if the 'ms' argument is zero. */
|
||||
int anetRecvTimeout(char *err, int fd, long long ms) {
|
||||
struct timeval tv;
|
||||
|
||||
tv.tv_sec = ms/1000;
|
||||
tv.tv_usec = (ms%1000)*1000;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_RCVTIMEO: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
|
||||
* do the actual work. It resolves the hostname "host" and set the string
|
||||
* representation of the IP address into the buffer pointed by "ipbuf".
|
||||
|
@ -70,6 +70,7 @@ int anetEnableTcpNoDelay(char *err, int fd);
|
||||
int anetDisableTcpNoDelay(char *err, int fd);
|
||||
int anetTcpKeepAlive(char *err, int fd);
|
||||
int anetSendTimeout(char *err, int fd, long long ms);
|
||||
int anetRecvTimeout(char *err, int fd, long long ms);
|
||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
|
||||
int anetKeepAlive(char *err, int fd, int interval);
|
||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
|
||||
|
139
src/aof.c
139
src/aof.c
@ -197,6 +197,12 @@ ssize_t aofRewriteBufferWrite(int fd) {
|
||||
* AOF file implementation
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Return true if an AOf fsync is currently already in progress in a
|
||||
* BIO thread. */
|
||||
int aofFsyncInProgress(void) {
|
||||
return bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
|
||||
}
|
||||
|
||||
/* Starts a background task that performs fsync() against the specified
|
||||
* file descriptor (the one of the AOF file) in another thread. */
|
||||
void aof_background_fsync(int fd) {
|
||||
@ -204,7 +210,7 @@ void aof_background_fsync(int fd) {
|
||||
}
|
||||
|
||||
/* Kills an AOFRW child process if exists */
|
||||
static void killAppendOnlyChild(void) {
|
||||
void killAppendOnlyChild(void) {
|
||||
int statloc;
|
||||
/* No AOFRW child? return. */
|
||||
if (server.aof_child_pid == -1) return;
|
||||
@ -221,6 +227,8 @@ static void killAppendOnlyChild(void) {
|
||||
server.aof_rewrite_time_start = -1;
|
||||
/* Close pipes used for IPC between the two processes. */
|
||||
aofClosePipes();
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
|
||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||
@ -261,7 +269,7 @@ int startAppendOnly(void) {
|
||||
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 {
|
||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||
* start a new one: the old one cannot be reused becuase it is not
|
||||
* start a new one: the old one cannot be reused because it is not
|
||||
* accumulating the AOF buffer. */
|
||||
if (server.aof_child_pid != -1) {
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
|
||||
@ -295,9 +303,7 @@ ssize_t aofWrite(int fd, const char *buf, size_t len) {
|
||||
nwritten = write(fd, buf, len);
|
||||
|
||||
if (nwritten < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (errno == EINTR) continue;
|
||||
return totwritten ? totwritten : -1;
|
||||
}
|
||||
|
||||
@ -333,10 +339,24 @@ void flushAppendOnlyFile(int force) {
|
||||
int sync_in_progress = 0;
|
||||
mstime_t latency;
|
||||
|
||||
if (sdslen(server.aof_buf) == 0) return;
|
||||
if (sdslen(server.aof_buf) == 0) {
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* because previously in AOF_FSYNC_EVERYSEC mode, fsync is
|
||||
* called only when aof buffer is not empty, so if users
|
||||
* stop write commands before fsync called in one second,
|
||||
* the data in page cache cannot be flushed in time. */
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.aof_fsync_offset != server.aof_current_size &&
|
||||
server.unixtime > server.aof_last_fsync &&
|
||||
!(sync_in_progress = aofFsyncInProgress())) {
|
||||
goto try_fsync;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
|
||||
sync_in_progress = aofFsyncInProgress();
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
|
||||
/* With this append fsync policy we do background fsyncing.
|
||||
@ -468,6 +488,7 @@ void flushAppendOnlyFile(int force) {
|
||||
server.aof_buf = sdsempty();
|
||||
}
|
||||
|
||||
try_fsync:
|
||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||
* children doing I/O in the background. */
|
||||
if (server.aof_no_fsync_on_rewrite &&
|
||||
@ -482,10 +503,14 @@ void flushAppendOnlyFile(int force) {
|
||||
redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.unixtime > server.aof_last_fsync)) {
|
||||
if (!sync_in_progress) aof_background_fsync(server.aof_fd);
|
||||
if (!sync_in_progress) {
|
||||
aof_background_fsync(server.aof_fd);
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
}
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
}
|
||||
}
|
||||
@ -645,7 +670,9 @@ struct client *createFakeClient(void) {
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
c->resp = 2;
|
||||
c->user = NULL;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
@ -677,18 +704,20 @@ int loadAppendOnlyFile(char *filename) {
|
||||
int old_aof_state = server.aof_state;
|
||||
long loops = 0;
|
||||
off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */
|
||||
off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */
|
||||
|
||||
if (fp == NULL) {
|
||||
serverLog(LL_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Handle a zero-length AOF file as a special case. An emtpy AOF file
|
||||
/* Handle a zero-length AOF file as a special case. An empty AOF file
|
||||
* is a valid AOF because an empty server with AOF enabled will create
|
||||
* a zero length file at startup, that will remain like that if no write
|
||||
* operation is received. */
|
||||
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
|
||||
server.aof_current_size = 0;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
fclose(fp);
|
||||
return C_ERR;
|
||||
}
|
||||
@ -698,7 +727,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
server.aof_state = AOF_OFF;
|
||||
|
||||
fakeClient = createFakeClient();
|
||||
startLoading(fp);
|
||||
startLoadingFile(fp, filename);
|
||||
|
||||
/* Check if this AOF file has an RDB preamble. In that case we need to
|
||||
* load the RDB file and later continue loading the AOF tail. */
|
||||
@ -777,16 +806,28 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* Command lookup */
|
||||
cmd = lookupCommand(argv[0]->ptr);
|
||||
if (!cmd) {
|
||||
serverLog(LL_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
|
||||
serverLog(LL_WARNING,
|
||||
"Unknown command '%s' reading the append only file",
|
||||
(char*)argv[0]->ptr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
|
||||
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->cmd = cmd;
|
||||
cmd->proc(fakeClient);
|
||||
if (fakeClient->flags & CLIENT_MULTI &&
|
||||
fakeClient->cmd->proc != execCommand)
|
||||
{
|
||||
queueMultiCommand(fakeClient);
|
||||
} else {
|
||||
cmd->proc(fakeClient);
|
||||
}
|
||||
|
||||
/* The fake client should not have a reply */
|
||||
serverAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
|
||||
serverAssert(fakeClient->bufpos == 0 &&
|
||||
listLength(fakeClient->reply) == 0);
|
||||
|
||||
/* The fake client should never get blocked */
|
||||
serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0);
|
||||
|
||||
@ -798,8 +839,15 @@ int loadAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
* If the client is in the middle of a MULTI/EXEC, log error and quit. */
|
||||
if (fakeClient->flags & CLIENT_MULTI) goto uxeof;
|
||||
* If the client is in the middle of a MULTI/EXEC, handle it as it was
|
||||
* a short read, even if technically the protocol is correct: we want
|
||||
* to remove the unprocessed tail and continue. */
|
||||
if (fakeClient->flags & CLIENT_MULTI) {
|
||||
serverLog(LL_WARNING,
|
||||
"Revert incomplete MULTI/EXEC transaction in AOF file");
|
||||
valid_up_to = valid_before_multi;
|
||||
goto uxeof;
|
||||
}
|
||||
|
||||
loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
fclose(fp);
|
||||
@ -808,6 +856,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
stopLoading();
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
return C_OK;
|
||||
|
||||
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
@ -1119,25 +1168,47 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
streamID id;
|
||||
int64_t numfields;
|
||||
|
||||
/* Reconstruct the stream data using XADD commands. */
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
/* Emit a two elements array for each item. The first is
|
||||
* the ID, the second is an array of field-value pairs. */
|
||||
if (s->length) {
|
||||
/* Reconstruct the stream data using XADD commands. */
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
/* Emit a two elements array for each item. The first is
|
||||
* the ID, the second is an array of field-value pairs. */
|
||||
|
||||
/* Emit the XADD <key> <id> ...fields... command. */
|
||||
if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0;
|
||||
/* Emit the XADD <key> <id> ...fields... command. */
|
||||
if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&id) == 0) return 0;
|
||||
while(numfields--) {
|
||||
unsigned char *field, *value;
|
||||
int64_t field_len, value_len;
|
||||
streamIteratorGetField(&si,&field,&value,&field_len,&value_len);
|
||||
if (rioWriteBulkString(r,(char*)field,field_len) == 0) return 0;
|
||||
if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Use the XADD MAXLEN 0 trick to generate an empty stream if
|
||||
* the key we are serializing is an empty string, which is possible
|
||||
* for the Stream type. */
|
||||
if (rioWriteBulkCount(r,'*',7) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&id) == 0) return 0;
|
||||
while(numfields--) {
|
||||
unsigned char *field, *value;
|
||||
int64_t field_len, value_len;
|
||||
streamIteratorGetField(&si,&field,&value,&field_len,&value_len);
|
||||
if (rioWriteBulkString(r,(char*)field,field_len) == 0) return 0;
|
||||
if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0;
|
||||
}
|
||||
if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"0",1) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"x",1) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"y",1) == 0) return 0;
|
||||
}
|
||||
|
||||
/* Append XSETID after XADD, make sure lastid is correct,
|
||||
* in case of XDEL lastid. */
|
||||
if (rioWriteBulkCount(r,'*',3) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XSETID",6) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
|
||||
|
||||
|
||||
/* Create all the stream consumer groups. */
|
||||
if (s->cgroups) {
|
||||
raxIterator ri;
|
||||
@ -1193,7 +1264,7 @@ int rewriteModuleObject(rio *r, robj *key, robj *o) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,r);
|
||||
moduleInitIOContext(io,mt,r,key);
|
||||
mt->aof_rewrite(&io,key,mv->value);
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
@ -1565,6 +1636,9 @@ void aofRemoveTempFile(pid_t childpid) {
|
||||
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* Update the server.aof_current_size field explicitly using stat(2)
|
||||
@ -1692,6 +1766,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
server.aof_current_size = server.aof_current_size;
|
||||
|
||||
/* Clear regular AOF buffer since its contents was just written to
|
||||
* the new AOF from the background rewrite buffer. */
|
||||
@ -1713,7 +1788,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error conditon. */
|
||||
* tirggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
serverLog(LL_WARNING,
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* This file implements atomic counters using __atomic or __sync macros if
|
||||
* available, otherwise synchronizing different threads using a mutex.
|
||||
*
|
||||
* The exported interaface is composed of three macros:
|
||||
* The exported interface is composed of three macros:
|
||||
*
|
||||
* atomicIncr(var,count) -- Increment the atomic counter
|
||||
* atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter
|
||||
@ -16,7 +16,7 @@
|
||||
* pthread_mutex_t myvar_mutex;
|
||||
* atomicSet(myvar,12345);
|
||||
*
|
||||
* If atomic primitives are availble (tested in config.h) the mutex
|
||||
* If atomic primitives are available (tested in config.h) the mutex
|
||||
* is not used.
|
||||
*
|
||||
* Never use return value from the macros, instead use the AtomicGetIncr()
|
||||
|
@ -17,7 +17,7 @@
|
||||
*
|
||||
* The design is trivial, we have a structure representing a job to perform
|
||||
* and a different thread and job queue for every job type.
|
||||
* Every thread wait for new jobs in its queue, and process every job
|
||||
* Every thread waits for new jobs in its queue, and process every job
|
||||
* sequentially.
|
||||
*
|
||||
* Jobs of the same type are guaranteed to be processed from the least
|
||||
@ -204,14 +204,14 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
}
|
||||
zfree(job);
|
||||
|
||||
/* Unblock threads blocked on bioWaitStepOfType() if any. */
|
||||
pthread_cond_broadcast(&bio_step_cond[type]);
|
||||
|
||||
/* Lock again before reiterating the loop, if there are no longer
|
||||
* jobs to process we'll block again in pthread_cond_wait(). */
|
||||
pthread_mutex_lock(&bio_mutex[type]);
|
||||
listDelNode(bio_jobs[type],ln);
|
||||
bio_pending[type]--;
|
||||
|
||||
/* Unblock threads blocked on bioWaitStepOfType() if any. */
|
||||
pthread_cond_broadcast(&bio_step_cond[type]);
|
||||
}
|
||||
}
|
||||
|
||||
|
22
src/bitops.c
22
src/bitops.c
@ -918,7 +918,7 @@ void bitfieldCommand(client *c) {
|
||||
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
|
||||
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
|
||||
int readonly = 1;
|
||||
size_t higest_write_offset = 0;
|
||||
size_t highest_write_offset = 0;
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
int remargs = c->argc-j-1; /* Remaining args other than current. */
|
||||
@ -968,8 +968,8 @@ void bitfieldCommand(client *c) {
|
||||
|
||||
if (opcode != BITFIELDOP_GET) {
|
||||
readonly = 0;
|
||||
if (higest_write_offset < bitoffset + bits - 1)
|
||||
higest_write_offset = bitoffset + bits - 1;
|
||||
if (highest_write_offset < bitoffset + bits - 1)
|
||||
highest_write_offset = bitoffset + bits - 1;
|
||||
/* INCRBY and SET require another argument. */
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j+3],&i64,NULL) != C_OK){
|
||||
zfree(ops);
|
||||
@ -994,15 +994,21 @@ void bitfieldCommand(client *c) {
|
||||
/* Lookup for read is ok if key doesn't exit, but errors
|
||||
* if it's not a string. */
|
||||
o = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (o != NULL && checkType(c,o,OBJ_STRING)) return;
|
||||
if (o != NULL && checkType(c,o,OBJ_STRING)) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/* Lookup by making room up to the farest bit reached by
|
||||
* this operation. */
|
||||
if ((o = lookupStringForBitCommand(c,
|
||||
higest_write_offset)) == NULL) return;
|
||||
highest_write_offset)) == NULL) {
|
||||
zfree(ops);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,numops);
|
||||
addReplyArrayLen(c,numops);
|
||||
|
||||
/* Actually process the operations. */
|
||||
for (j = 0; j < numops; j++) {
|
||||
@ -1047,7 +1053,7 @@ void bitfieldCommand(client *c) {
|
||||
setSignedBitfield(o->ptr,thisop->offset,
|
||||
thisop->bits,newval);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
} else {
|
||||
uint64_t oldval, newval, wrapped, retval;
|
||||
@ -1076,7 +1082,7 @@ void bitfieldCommand(client *c) {
|
||||
setUnsignedBitfield(o->ptr,thisop->offset,
|
||||
thisop->bits,newval);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
}
|
||||
changes++;
|
||||
|
117
src/blocked.c
117
src/blocked.c
@ -77,10 +77,18 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
|
||||
* is zero. */
|
||||
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit) {
|
||||
long long tval;
|
||||
long double ftval;
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,object,&tval,
|
||||
"timeout is not an integer or out of range") != C_OK)
|
||||
return C_ERR;
|
||||
if (unit == UNIT_SECONDS) {
|
||||
if (getLongDoubleFromObjectOrReply(c,object,&ftval,
|
||||
"timeout is not an float or out of range") != C_OK)
|
||||
return C_ERR;
|
||||
tval = (long long) (ftval * 1000.0);
|
||||
} else {
|
||||
if (getLongLongFromObjectOrReply(c,object,&tval,
|
||||
"timeout is not an integer or out of range") != C_OK)
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (tval < 0) {
|
||||
addReplyError(c,"timeout is negative");
|
||||
@ -88,7 +96,6 @@ int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int
|
||||
}
|
||||
|
||||
if (tval > 0) {
|
||||
if (unit == UNIT_SECONDS) tval *= 1000;
|
||||
tval += mstime();
|
||||
}
|
||||
*timeout = tval;
|
||||
@ -126,12 +133,37 @@ void processUnblockedClients(void) {
|
||||
* the code is conceptually more correct this way. */
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||
processInputBuffer(c);
|
||||
processInputBufferAndReplicate(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This function will schedule the client for reprocessing at a safe time.
|
||||
*
|
||||
* This is useful when a client was blocked for some reason (blocking opeation,
|
||||
* CLIENT PAUSE, or whatever), because it may end with some accumulated query
|
||||
* buffer that needs to be processed ASAP:
|
||||
*
|
||||
* 1. When a client is blocked, its readable handler is still active.
|
||||
* 2. However in this case it only gets data into the query buffer, but the
|
||||
* query is not parsed or executed once there is enough to proceed as
|
||||
* usually (because the client is blocked... so we can't execute commands).
|
||||
* 3. When the client is unblocked, without this function, the client would
|
||||
* have to write some query in order for the readable handler to finally
|
||||
* call processQueryBuffer*() on it.
|
||||
* 4. With this function instead we can put the client in a queue that will
|
||||
* process it for queries ready to be executed at a safe time.
|
||||
*/
|
||||
void queueClientForReprocessing(client *c) {
|
||||
/* The client may already be into the unblocked list because of a previous
|
||||
* blocking operation, don't add back it into the list multiple times. */
|
||||
if (!(c->flags & CLIENT_UNBLOCKED)) {
|
||||
c->flags |= CLIENT_UNBLOCKED;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
}
|
||||
|
||||
/* Unblock a client calling the right function depending on the kind
|
||||
* of operation the client is blocking for. */
|
||||
void unblockClient(client *c) {
|
||||
@ -152,12 +184,7 @@ void unblockClient(client *c) {
|
||||
server.blocked_clients_by_type[c->btype]--;
|
||||
c->flags &= ~CLIENT_BLOCKED;
|
||||
c->btype = BLOCKED_NONE;
|
||||
/* The client may already be into the unblocked list because of a previous
|
||||
* blocking operation, don't add back it into the list multiple times. */
|
||||
if (!(c->flags & CLIENT_UNBLOCKED)) {
|
||||
c->flags |= CLIENT_UNBLOCKED;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
queueClientForReprocessing(c);
|
||||
}
|
||||
|
||||
/* This function gets called when a blocked client timed out in order to
|
||||
@ -167,7 +194,7 @@ void replyToBlockedClientTimedOut(client *c) {
|
||||
if (c->btype == BLOCKED_LIST ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
} else if (c->btype == BLOCKED_WAIT) {
|
||||
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
@ -195,7 +222,7 @@ void disconnectAllBlockedClients(void) {
|
||||
if (c->flags & CLIENT_BLOCKED) {
|
||||
addReplySds(c,sdsnew(
|
||||
"-UNBLOCKED force unblock from blocking operation, "
|
||||
"instance state changed (master -> slave?)\r\n"));
|
||||
"instance state changed (master -> replica?)\r\n"));
|
||||
unblockClient(c);
|
||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||
}
|
||||
@ -269,7 +296,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
robj *dstkey = receiver->bpop.target;
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == blpopCommand) ?
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
robj *value = listTypePop(o,where);
|
||||
|
||||
if (value) {
|
||||
@ -285,7 +312,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
{
|
||||
/* If we failed serving the client we need
|
||||
* to also undo the POP operation. */
|
||||
listTypePush(o,value,where);
|
||||
listTypePush(o,value,where);
|
||||
}
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
@ -370,40 +397,58 @@ void handleClientsBlockedOnKeys(void) {
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
streamID *gt = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
if (s->last_id.ms > gt->ms ||
|
||||
(s->last_id.ms == gt->ms &&
|
||||
s->last_id.seq > gt->seq))
|
||||
{
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
* last ID the client is blocked for: this is needed
|
||||
* because serving other clients in the same consumer
|
||||
* group will alter the "last ID" of the consumer
|
||||
* group, and clients blocked in a consumer group are
|
||||
* always blocked for the ">" ID: we need to deliver
|
||||
* only new messages and avoid unblocking the client
|
||||
* otherwise. */
|
||||
streamCG *group = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
receiver->bpop.xread_group->ptr);
|
||||
/* If the group was not found, send an error
|
||||
* to the consumer. */
|
||||
if (!group) {
|
||||
addReplyError(receiver,
|
||||
"-NOGROUP the consumer group this client "
|
||||
"was blocked on no longer exists");
|
||||
unblockClient(receiver);
|
||||
continue;
|
||||
} else {
|
||||
*gt = group->last_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
start.seq++; /* Can't overflow, it's an uint64_t */
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and
|
||||
* consumer here. */
|
||||
streamCG *group = NULL;
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
receiver->bpop.xread_group->ptr);
|
||||
/* In theory if the group is not found we
|
||||
* just perform the read without the group,
|
||||
* but actually when the group, or the key
|
||||
* itself is deleted (triggering the removal
|
||||
* of the group), we check for blocked clients
|
||||
* and send them an error. */
|
||||
}
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
receiver->bpop.xread_consumer->ptr,
|
||||
1);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
/* Emit the two elements sub-array consisting of
|
||||
* the name of the stream and the data we
|
||||
* extracted from it. Wrapped in a single-item
|
||||
* array, since we have just one key. */
|
||||
addReplyMultiBulkLen(receiver,1);
|
||||
addReplyMultiBulkLen(receiver,2);
|
||||
if (receiver->resp == 2) {
|
||||
addReplyArrayLen(receiver,1);
|
||||
addReplyArrayLen(receiver,2);
|
||||
} else {
|
||||
addReplyMapLen(receiver,1);
|
||||
}
|
||||
addReplyBulk(receiver,rl->key);
|
||||
|
||||
streamPropInfo pi = {
|
||||
@ -412,7 +457,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
};
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, 0, &pi);
|
||||
0, group, consumer, noack, &pi);
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
|
170
src/cluster.c
170
src/cluster.c
@ -1230,7 +1230,7 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Clear FAIL state for node %.40s: %s is reachable again.",
|
||||
node->name,
|
||||
nodeIsSlave(node) ? "slave" : "master without slots");
|
||||
nodeIsSlave(node) ? "replica" : "master without slots");
|
||||
node->flags &= ~CLUSTER_NODE_FAIL;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
@ -1589,6 +1589,12 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
|
||||
}
|
||||
}
|
||||
|
||||
/* After updating the slots configuration, don't do any actual change
|
||||
* in the state of the server if a module disabled Redis Cluster
|
||||
* keys redirections. */
|
||||
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
|
||||
return;
|
||||
|
||||
/* If at least one slot was reassigned from a node to another node
|
||||
* with a greater configEpoch, it is possible that:
|
||||
* 1) We are a master left without slots. This means that we were
|
||||
@ -2059,7 +2065,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
|
||||
server.cluster->mf_slave = sender;
|
||||
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
|
||||
serverLog(LL_WARNING,"Manual failover requested by slave %.40s.",
|
||||
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
||||
sender->name);
|
||||
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
|
||||
clusterNode *n; /* The node the update is about. */
|
||||
@ -2873,7 +2879,7 @@ void clusterLogCantFailover(int reason) {
|
||||
switch(reason) {
|
||||
case CLUSTER_CANT_FAILOVER_DATA_AGE:
|
||||
msg = "Disconnected from master for longer than allowed. "
|
||||
"Please check the 'cluster-slave-validity-factor' configuration "
|
||||
"Please check the 'cluster-replica-validity-factor' configuration "
|
||||
"option.";
|
||||
break;
|
||||
case CLUSTER_CANT_FAILOVER_WAITING_DELAY:
|
||||
@ -3025,6 +3031,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
if (server.cluster->mf_end) {
|
||||
server.cluster->failover_auth_time = mstime();
|
||||
server.cluster->failover_auth_rank = 0;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER);
|
||||
}
|
||||
serverLog(LL_WARNING,
|
||||
"Start of election delayed for %lld milliseconds "
|
||||
@ -3054,7 +3061,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
server.cluster->failover_auth_time += added_delay;
|
||||
server.cluster->failover_auth_rank = newrank;
|
||||
serverLog(LL_WARNING,
|
||||
"Slave rank updated to #%d, added %lld milliseconds of delay.",
|
||||
"Replica rank updated to #%d, added %lld milliseconds of delay.",
|
||||
newrank, added_delay);
|
||||
}
|
||||
}
|
||||
@ -3100,7 +3107,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
(unsigned long long) myself->configEpoch);
|
||||
}
|
||||
|
||||
/* Take responsability for the cluster slots. */
|
||||
/* Take responsibility for the cluster slots. */
|
||||
clusterFailoverReplaceYourMaster();
|
||||
} else {
|
||||
clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_VOTES);
|
||||
@ -3151,11 +3158,11 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
!nodeTimedOut(mymaster->slaves[j])) okslaves++;
|
||||
if (okslaves <= server.cluster_migration_barrier) return;
|
||||
|
||||
/* Step 3: Idenitfy a candidate for migration, and check if among the
|
||||
/* Step 3: Identify a candidate for migration, and check if among the
|
||||
* masters with the greatest number of ok slaves, I'm the one with the
|
||||
* smallest node ID (the "candidate slave").
|
||||
*
|
||||
* Note: this means that eventually a replica migration will occurr
|
||||
* Note: this means that eventually a replica migration will occur
|
||||
* since slaves that are reachable again always have their FAIL flag
|
||||
* cleared, so eventually there must be a candidate. At the same time
|
||||
* this does not mean that there are no race conditions possible (two
|
||||
@ -3210,7 +3217,8 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
* the natural slaves of this instance to advertise their switch from
|
||||
* the old master to the new one. */
|
||||
if (target && candidate == myself &&
|
||||
(mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY)
|
||||
(mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY &&
|
||||
!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
|
||||
{
|
||||
serverLog(LL_WARNING,"Migrating to orphaned master %.40s",
|
||||
target->name);
|
||||
@ -3321,14 +3329,18 @@ void clusterCron(void) {
|
||||
int changed = 0;
|
||||
|
||||
if (prev_ip == NULL && curr_ip != NULL) changed = 1;
|
||||
if (prev_ip != NULL && curr_ip == NULL) changed = 1;
|
||||
if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
|
||||
else if (prev_ip != NULL && curr_ip == NULL) changed = 1;
|
||||
else if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
|
||||
|
||||
if (changed) {
|
||||
if (prev_ip) zfree(prev_ip);
|
||||
prev_ip = curr_ip;
|
||||
if (prev_ip) prev_ip = zstrdup(prev_ip);
|
||||
|
||||
if (curr_ip) {
|
||||
/* We always take a copy of the previous IP address, by
|
||||
* duplicating the string. This way later we can check if
|
||||
* the address really changed. */
|
||||
prev_ip = zstrdup(prev_ip);
|
||||
strncpy(myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN);
|
||||
myself->ip[NET_IP_STR_LEN-1] = '\0';
|
||||
} else {
|
||||
@ -3559,7 +3571,8 @@ void clusterCron(void) {
|
||||
|
||||
if (nodeIsSlave(myself)) {
|
||||
clusterHandleManualFailover();
|
||||
clusterHandleSlaveFailover();
|
||||
if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
|
||||
clusterHandleSlaveFailover();
|
||||
/* If there are orphaned slaves, and we are a slave among the masters
|
||||
* with the max number of non-failing slaves, consider migrating to
|
||||
* the orphaned masters. Note that it does not make sense to try
|
||||
@ -3736,7 +3749,7 @@ void clusterCloseAllSlots(void) {
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* The following are defines that are only used in the evaluation function
|
||||
* and are based on heuristics. Actaully the main point about the rejoin and
|
||||
* and are based on heuristics. Actually the main point about the rejoin and
|
||||
* writable delay is that they should be a few orders of magnitude larger
|
||||
* than the network latency. */
|
||||
#define CLUSTER_MAX_REJOIN_DELAY 5000
|
||||
@ -3865,6 +3878,11 @@ int verifyClusterConfigWithData(void) {
|
||||
int j;
|
||||
int update_config = 0;
|
||||
|
||||
/* Return ASAP if a module disabled cluster redirections. In that case
|
||||
* every master can store keys about every possible hash slot. */
|
||||
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
|
||||
return C_OK;
|
||||
|
||||
/* If this node is a slave, don't perform the check at all as we
|
||||
* completely depend on the replication stream. */
|
||||
if (nodeIsSlave(myself)) return C_OK;
|
||||
@ -4109,7 +4127,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
*/
|
||||
|
||||
int num_masters = 0;
|
||||
void *slot_replylen = addDeferredMultiBulkLength(c);
|
||||
void *slot_replylen = addReplyDeferredLen(c);
|
||||
|
||||
dictEntry *de;
|
||||
dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
|
||||
@ -4129,7 +4147,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
int nested_elements = 3; /* slots (2) + master addr (1). */
|
||||
void *nested_replylen = addDeferredMultiBulkLength(c);
|
||||
void *nested_replylen = addReplyDeferredLen(c);
|
||||
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
@ -4145,7 +4163,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
start = -1;
|
||||
|
||||
/* First node reply position is always the master */
|
||||
addReplyMultiBulkLen(c, 3);
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
@ -4155,19 +4173,19 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyMultiBulkLen(c, 3);
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
|
||||
setDeferredArrayLen(c, nested_replylen, nested_elements);
|
||||
num_masters++;
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredMultiBulkLength(c, slot_replylen, num_masters);
|
||||
setDeferredArrayLen(c, slot_replylen, num_masters);
|
||||
}
|
||||
|
||||
void clusterCommand(client *c) {
|
||||
@ -4178,27 +4196,27 @@ void clusterCommand(client *c) {
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"addslots <slot> [slot ...] -- Assign slots to current node.",
|
||||
"bumpepoch -- Advance the cluster config epoch.",
|
||||
"count-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
|
||||
"countkeysinslot <slot> - Return the number of keys in <slot>.",
|
||||
"delslots <slot> [slot ...] -- Delete slots information from current node.",
|
||||
"failover [force|takeover] -- Promote current slave node to being a master.",
|
||||
"forget <node-id> -- Remove a node from the cluster.",
|
||||
"getkeysinslot <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"flushslots -- Delete current node own slots information.",
|
||||
"info - Return onformation about the cluster.",
|
||||
"keyslot <key> -- Return the hash slot for <key>.",
|
||||
"meet <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
||||
"myid -- Return the node id.",
|
||||
"nodes -- Return cluster configuration seen by node. Output format:",
|
||||
"ADDSLOTS <slot> [slot ...] -- Assign slots to current node.",
|
||||
"BUMPEPOCH -- Advance the cluster config epoch.",
|
||||
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
|
||||
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.",
|
||||
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.",
|
||||
"FAILOVER [force|takeover] -- Promote current replica node to being a master.",
|
||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
||||
"INFO - Return onformation about the cluster.",
|
||||
"KEYSLOT <key> -- Return the hash slot for <key>.",
|
||||
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
||||
"MYID -- Return the node id.",
|
||||
"NODES -- Return cluster configuration seen by node. Output format:",
|
||||
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>",
|
||||
"replicate <node-id> -- Configure current node as slave to <node-id>.",
|
||||
"reset [hard|soft] -- Reset current node (default: soft).",
|
||||
"set-config-epoch <epoch> - Set config epoch of current node.",
|
||||
"setslot <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
||||
"slaves <node-id> -- Return <node-id> slaves.",
|
||||
"slots -- Return information about slots range mappings. Each range is made of:",
|
||||
"REPLICATE <node-id> -- Configure current node as replica to <node-id>.",
|
||||
"RESET [hard|soft] -- Reset current node (default: soft).",
|
||||
"SET-config-epoch <epoch> - Set config epoch of current node.",
|
||||
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
||||
"REPLICAS <node-id> -- Return <node-id> replicas.",
|
||||
"SLOTS -- Return information about slots range mappings. Each range is made of:",
|
||||
" start, end, master and replicas IP addresses, ports and ids",
|
||||
NULL
|
||||
};
|
||||
@ -4233,12 +4251,7 @@ NULL
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
|
||||
/* CLUSTER NODES */
|
||||
robj *o;
|
||||
sds ci = clusterGenNodesDescription(0);
|
||||
|
||||
o = createObject(OBJ_STRING,ci);
|
||||
addReplyBulk(c,o);
|
||||
decrRefCount(o);
|
||||
addReplyBulkSds(c,clusterGenNodesDescription(0));
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
|
||||
/* CLUSTER MYID */
|
||||
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
|
||||
@ -4531,7 +4544,7 @@ NULL
|
||||
|
||||
keys = zmalloc(sizeof(robj*)*maxkeys);
|
||||
numkeys = getKeysInSlot(slot, keys, maxkeys);
|
||||
addReplyMultiBulkLen(c,numkeys);
|
||||
addReplyArrayLen(c,numkeys);
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
addReplyBulk(c,keys[j]);
|
||||
decrRefCount(keys[j]);
|
||||
@ -4574,7 +4587,7 @@ NULL
|
||||
|
||||
/* Can't replicate a slave. */
|
||||
if (nodeIsSlave(n)) {
|
||||
addReplyError(c,"I can only replicate a master, not a slave.");
|
||||
addReplyError(c,"I can only replicate a master, not a replica.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4593,7 +4606,8 @@ NULL
|
||||
clusterSetMaster(n);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"slaves") && c->argc == 3) {
|
||||
} else if ((!strcasecmp(c->argv[1]->ptr,"slaves") ||
|
||||
!strcasecmp(c->argv[1]->ptr,"replicas")) && c->argc == 3) {
|
||||
/* CLUSTER SLAVES <NODE ID> */
|
||||
clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
|
||||
int j;
|
||||
@ -4609,7 +4623,7 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyMultiBulkLen(c,n->numslaves);
|
||||
addReplyArrayLen(c,n->numslaves);
|
||||
for (j = 0; j < n->numslaves; j++) {
|
||||
sds ni = clusterGenNodeDescription(n->slaves[j]);
|
||||
addReplyBulkCString(c,ni);
|
||||
@ -4647,10 +4661,10 @@ NULL
|
||||
|
||||
/* Check preconditions. */
|
||||
if (nodeIsMaster(myself)) {
|
||||
addReplyError(c,"You should send CLUSTER FAILOVER to a slave");
|
||||
addReplyError(c,"You should send CLUSTER FAILOVER to a replica");
|
||||
return;
|
||||
} else if (myself->slaveof == NULL) {
|
||||
addReplyError(c,"I'm a slave but my master is unknown to me");
|
||||
addReplyError(c,"I'm a replica but my master is unknown to me");
|
||||
return;
|
||||
} else if (!force &&
|
||||
(nodeFailed(myself->slaveof) ||
|
||||
@ -4746,8 +4760,7 @@ NULL
|
||||
clusterReset(hard);
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLUSTER HELP",
|
||||
(char*)c->argv[1]->ptr);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -4758,7 +4771,7 @@ NULL
|
||||
|
||||
/* Generates a DUMP-format representation of the object 'o', adding it to the
|
||||
* io stream pointed by 'rio'. This function can't fail. */
|
||||
void createDumpPayload(rio *payload, robj *o) {
|
||||
void createDumpPayload(rio *payload, robj *o, robj *key) {
|
||||
unsigned char buf[2];
|
||||
uint64_t crc;
|
||||
|
||||
@ -4766,7 +4779,7 @@ void createDumpPayload(rio *payload, robj *o) {
|
||||
* byte followed by the serialized object. This is understood by RESTORE. */
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
serverAssert(rdbSaveObjectType(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o,key));
|
||||
|
||||
/* Write the footer, this is how it looks like:
|
||||
* ----------------+---------------------+---------------+
|
||||
@ -4814,28 +4827,26 @@ int verifyDumpPayload(unsigned char *p, size_t len) {
|
||||
* DUMP is actually not used by Redis Cluster but it is the obvious
|
||||
* complement of RESTORE and can be useful for different applications. */
|
||||
void dumpCommand(client *c) {
|
||||
robj *o, *dumpobj;
|
||||
robj *o;
|
||||
rio payload;
|
||||
|
||||
/* Check if the key is here. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the DUMP encoded representation. */
|
||||
createDumpPayload(&payload,o);
|
||||
createDumpPayload(&payload,o,c->argv[1]);
|
||||
|
||||
/* Transfer to the client */
|
||||
dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
|
||||
addReplyBulk(c,dumpobj);
|
||||
decrRefCount(dumpobj);
|
||||
addReplyBulkSds(c,payload.io.buffer.ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* RESTORE key ttl serialized-value [REPLACE] */
|
||||
void restoreCommand(client *c) {
|
||||
long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock;
|
||||
long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock = -1;
|
||||
rio payload;
|
||||
int j, type, replace = 0, absttl = 0;
|
||||
robj *obj;
|
||||
@ -4897,7 +4908,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload)) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@ -5147,6 +5158,11 @@ try_again:
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
|
||||
}
|
||||
|
||||
int non_expired = 0; /* Number of keys that we'll find non expired.
|
||||
Note that serializing large keys may take some time
|
||||
so certain keys that were found non expired by the
|
||||
lookupKey() function, may be expired later. */
|
||||
|
||||
/* Create RESTORE payload and generate the protocol to call the command. */
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
long long ttl = 0;
|
||||
@ -5154,8 +5170,17 @@ try_again:
|
||||
|
||||
if (expireat != -1) {
|
||||
ttl = expireat-mstime();
|
||||
if (ttl < 0) {
|
||||
continue;
|
||||
}
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
|
||||
/* Relocate valid (non expired) keys into the array in successive
|
||||
* positions to remove holes created by the keys that were present
|
||||
* in the first lookup but are now expired after the second lookup. */
|
||||
kv[non_expired++] = kv[j];
|
||||
|
||||
serverAssertWithInfo(c,NULL,
|
||||
rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
|
||||
|
||||
@ -5171,7 +5196,7 @@ try_again:
|
||||
|
||||
/* Emit the payload argument, that is the serialized object using
|
||||
* the DUMP format. */
|
||||
createDumpPayload(&payload,ov[j]);
|
||||
createDumpPayload(&payload,ov[j],kv[j]);
|
||||
serverAssertWithInfo(c,NULL,
|
||||
rioWriteBulkString(&cmd,payload.io.buffer.ptr,
|
||||
sdslen(payload.io.buffer.ptr)));
|
||||
@ -5183,6 +5208,9 @@ try_again:
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"REPLACE",7));
|
||||
}
|
||||
|
||||
/* Fix the actual number of keys we are migrating. */
|
||||
num_keys = non_expired;
|
||||
|
||||
/* Transfer the query to the other node in 64K chunks. */
|
||||
errno = 0;
|
||||
{
|
||||
@ -5218,6 +5246,10 @@ try_again:
|
||||
int socket_error = 0;
|
||||
int del_idx = 1; /* Index of the key argument for the replicated DEL op. */
|
||||
|
||||
/* Allocate the new argument vector that will replace the current command,
|
||||
* to propagate the MIGRATE as a DEL command (if no COPY option was given).
|
||||
* We allocate num_keys+1 because the additional argument is for "DEL"
|
||||
* command name itself. */
|
||||
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
|
||||
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
@ -5418,9 +5450,17 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
multiCmd mc;
|
||||
int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0;
|
||||
|
||||
/* Allow any key to be set if a module disabled cluster redirections. */
|
||||
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
|
||||
return myself;
|
||||
|
||||
/* Set error code optimistically for the base case. */
|
||||
if (error_code) *error_code = CLUSTER_REDIR_NONE;
|
||||
|
||||
/* Modules can turn off Redis Cluster redirection: this is useful
|
||||
* when writing a module that implements a completely different
|
||||
* distributed system. */
|
||||
|
||||
/* We handle all the cases as if they were EXEC commands, so we have
|
||||
* a common code path for everything */
|
||||
if (cmd->proc == execCommand) {
|
||||
@ -5585,7 +5625,7 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
|
||||
if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
|
||||
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
|
||||
} else if (error_code == CLUSTER_REDIR_UNSTABLE) {
|
||||
/* The request spawns mutliple keys in the same slot,
|
||||
/* The request spawns multiple keys in the same slot,
|
||||
* but the slot is not "stable" currently as there is
|
||||
* a migration or import in progress. */
|
||||
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
|
||||
|
@ -100,6 +100,13 @@ typedef struct clusterLink {
|
||||
#define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */
|
||||
#define CLUSTERMSG_TYPE_COUNT 10 /* Total number of message types. */
|
||||
|
||||
/* Flags that a module can set in order to prevent certain Redis Cluster
|
||||
* features to be enabled. Useful when implementing a different distributed
|
||||
* system on top of Redis Cluster message bus, using modules. */
|
||||
#define CLUSTER_MODULE_FLAG_NONE 0
|
||||
#define CLUSTER_MODULE_FLAG_NO_FAILOVER (1<<1)
|
||||
#define CLUSTER_MODULE_FLAG_NO_REDIRECTION (1<<2)
|
||||
|
||||
/* This structure represent elements of node->fail_reports. */
|
||||
typedef struct clusterNodeFailReport {
|
||||
struct clusterNode *node; /* Node reporting the failure condition. */
|
||||
@ -243,7 +250,7 @@ union clusterMsgData {
|
||||
#define CLUSTER_PROTO_VER 1 /* Cluster bus protocol version. */
|
||||
|
||||
typedef struct {
|
||||
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
|
||||
char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t ver; /* Protocol version, currently set to 1. */
|
||||
uint16_t port; /* TCP base port number. */
|
||||
|
633
src/config.c
633
src/config.c
File diff suppressed because it is too large
Load Diff
@ -62,7 +62,9 @@
|
||||
#endif
|
||||
|
||||
/* Test for backtrace() */
|
||||
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__))
|
||||
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
|
||||
defined(__FreeBSD__) || (defined(__OpenBSD__) && defined(USE_BACKTRACE))\
|
||||
|| defined(__DragonFly__)
|
||||
#define HAVE_BACKTRACE 1
|
||||
#endif
|
||||
|
||||
|
835
src/crc16_slottable.h
Normal file
835
src/crc16_slottable.h
Normal file
@ -0,0 +1,835 @@
|
||||
#ifndef _CRC16_TABLE_H__
|
||||
#define _CRC16_TABLE_H__
|
||||
|
||||
/* A table of the shortest possible alphanumeric string that is mapped by redis' crc16
|
||||
* to any given redis cluster slot.
|
||||
*
|
||||
* The array indexes are slot numbers, so that given a desired slot, this string is guaranteed
|
||||
* to make redis cluster route a request to the shard holding this slot
|
||||
*/
|
||||
|
||||
const char *crc16_slot_table[] = {
|
||||
"06S", "Qi", "5L5", "4Iu", "4gY", "460", "1Y7", "1LV", "0QG", "ru", "7Ok", "4ji", "4DE", "65n", "2JH", "I8", "F9", "SX", "7nF", "4KD",
|
||||
"4eh", "6PK", "2ke", "1Ng", "0Sv", "4L", "491", "4hX", "4Ft", "5C4", "2Hy", "09R", "021", "0cX", "4Xv", "6mU", "6Cy", "42R", "0Mt", "nF",
|
||||
"cv", "1Pe", "5kK", "6NI", "74L", "4UF", "0nh", "MZ", "2TJ", "0ai", "4ZG", "6od", "6AH", "40c", "0OE", "lw", "aG", "0Bu", "5iz", "6Lx",
|
||||
"5R7", "4Ww", "0lY", "Ok", "5n3", "4ks", "8YE", "7g", "2KR", "1nP", "714", "64t", "69D", "4Ho", "07I", "Ps", "2hN", "1ML", "4fC", "7CA",
|
||||
"avs", "4iB", "0Rl", "5V", "2Ic", "08H", "4Gn", "66E", "aUo", "b4e", "05x", "RB", "8f", "8VD", "4dr", "5a2", "4zp", "6OS", "bl", "355",
|
||||
"0or", "1j2", "75V", "bno", "4Yl", "6lO", "Ap", "0bB", "0Ln", "2yM", "6Bc", "43H", "4xA", "6Mb", "22D", "14", "0mC", "Nq", "6cN", "4Vm",
|
||||
"ban", "aDl", "CA", "14Z", "8GG", "mm", "549", "41y", "53t", "464", "1Y3", "1LR", "06W", "Qm", "5L1", "4Iq", "4DA", "65j", "2JL", "1oN",
|
||||
"0QC", "6y", "7Oo", "4jm", "4el", "6PO", "9x", "1Nc", "04f", "2EM", "7nB", "bqs", "4Fp", "5C0", "d6F", "09V", "0Sr", "4H", "495", "bRo",
|
||||
"aio", "42V", "0Mp", "nB", "025", "17u", "4Xr", "6mQ", "74H", "4UB", "0nl", "3Kn", "cr", "1Pa", "5kO", "6NM", "6AL", "40g", "0OA", "ls",
|
||||
"2TN", "0am", "4ZC", "aEr", "5R3", "4Ws", "18t", "Oo", "aC", "0Bq", "bCl", "afn", "2KV", "1nT", "5Uz", "64p", "5n7", "4kw", "0PY", "7c",
|
||||
"2hJ", "1MH", "4fG", "6Sd", "7mi", "4Hk", "07M", "Pw", "2Ig", "08L", "4Gj", "66A", "7LD", "4iF", "0Rh", "5R", "8b", "1Oy", "4dv", "5a6",
|
||||
"7oX", "4JZ", "0qt", "RF", "0ov", "LD", "4A9", "4TX", "4zt", "6OW", "bh", "0AZ", "z9", "oX", "6Bg", "43L", "4Yh", "6lK", "At", "0bF",
|
||||
"0mG", "Nu", "6cJ", "4Vi", "4xE", "6Mf", "2vH", "10", "8GC", "mi", "5p5", "4uu", "5Kx", "4N8", "CE", "1pV", "0QO", "6u", "7Oc", "4ja",
|
||||
"4DM", "65f", "3Za", "I0", "0rS", "Qa", "68V", "b7F", "4gQ", "468", "dSo", "285", "274", "4D", "499", "4hP", "b8G", "67W", "0h3", "09Z",
|
||||
"F1", "SP", "7nN", "4KL", "51I", "6PC", "9t", "1No", "21g", "1Pm", "5kC", "6NA", "74D", "4UN", "X3", "MR", "029", "0cP", "bbM", "79t",
|
||||
"4c3", "42Z", "8Dd", "nN", "aO", "8Ke", "4yS", "4l2", "76u", "635", "0lQ", "Oc", "BS", "W2", "4ZO", "6ol", "7Qa", "40k", "0OM", "2zn",
|
||||
"69L", "4Hg", "07A", "2Fj", "2hF", "k6", "4fK", "6Sh", "7Ny", "6K9", "0PU", "7o", "2KZ", "1nX", "4EW", "4P6", "7oT", "4JV", "05p", "RJ",
|
||||
"8n", "1Ou", "4dz", "6QY", "7LH", "4iJ", "d7", "qV", "2Ik", "1li", "4Gf", "66M", "4Yd", "6lG", "Ax", "0bJ", "z5", "oT", "6Bk", "4wH",
|
||||
"4zx", "aeI", "bd", "0AV", "0oz", "LH", "4A5", "4TT", "5Kt", "4N4", "CI", "14R", "0NW", "me", "541", "41q", "4xI", "6Mj", "22L", "u4",
|
||||
"0mK", "Ny", "6cF", "4Ve", "4DI", "65b", "2JD", "I4", "0QK", "6q", "7Og", "4je", "4gU", "4r4", "2iX", "1LZ", "0rW", "Qe", "5L9", "4Iy",
|
||||
"4Fx", "5C8", "0h7", "1mw", "0Sz", "pH", "7MV", "4hT", "4ed", "6PG", "9p", "1Nk", "F5", "ST", "7nJ", "4KH", "7pH", "4UJ", "X7", "MV",
|
||||
"cz", "1Pi", "5kG", "6NE", "4c7", "4vV", "0Mx", "nJ", "0v5", "0cT", "4Xz", "6mY", "6bX", "5GZ", "0lU", "Og", "aK", "0By", "4yW", "4l6",
|
||||
"6AD", "40o", "0OI", "2zj", "BW", "W6", "4ZK", "6oh", "2hB", "k2", "4fO", "6Sl", "69H", "4Hc", "07E", "2Fn", "d5e", "83m", "4ES", "4P2",
|
||||
"a0F", "bQL", "0PQ", "7k", "8j", "1Oq", "50W", "hbv", "7oP", "4JR", "05t", "RN", "2Io", "08D", "4Gb", "66I", "7LL", "4iN", "d3", "5Z",
|
||||
"z1", "oP", "6Bo", "43D", "5IA", "6lC", "2Wm", "0bN", "8ff", "LL", "4A1", "4TP", "cPn", "aeM", "0T3", "0AR", "0NS", "ma", "545", "41u",
|
||||
"5Kp", "4N0", "CM", "14V", "0mO", "2Xl", "6cB", "4Va", "4xM", "6Mn", "22H", "18", "04s", "SI", "7nW", "4KU", "4ey", "6PZ", "9m", "1Nv",
|
||||
"e4", "pU", "7MK", "4hI", "4Fe", "67N", "2Hh", "09C", "06B", "Qx", "68O", "4Id", "4gH", "6Rk", "2iE", "j5", "0QV", "6l", "5o8", "4jx",
|
||||
"4DT", "4Q5", "2JY", "82j", "BJ", "0ax", "4ZV", "4O7", "552", "40r", "0OT", "lf", "aV", "t7", "4yJ", "6Li", "6bE", "4Wf", "0lH", "Oz",
|
||||
"2Vj", "0cI", "4Xg", "6mD", "6Ch", "42C", "0Me", "nW", "cg", "1Pt", "5kZ", "6NX", "7pU", "4UW", "0ny", "MK", "7LQ", "4iS", "267", "5G",
|
||||
"0i0", "08Y", "b9D", "66T", "7oM", "4JO", "G2", "RS", "8w", "1Ol", "4dc", "7Aa", "atS", "4kb", "0PL", "7v", "2KC", "H3", "4EN", "64e",
|
||||
"69U", "b6E", "07X", "Pb", "dRl", "296", "4fR", "4s3", "4xP", "4m1", "22U", "8Jf", "0mR", "0x3", "77v", "626", "5Km", "6no", "CP", "V1",
|
||||
"0NN", "3kL", "7Pb", "41h", "4za", "6OB", "20d", "0AO", "Y0", "LQ", "6an", "4TM", "bcN", "78w", "Aa", "0bS", "8Eg", "oM", "4b0", "43Y",
|
||||
"51T", "azL", "9i", "1Nr", "04w", "SM", "7nS", "4KQ", "4Fa", "67J", "2Hl", "09G", "e0", "4Y", "7MO", "4hM", "4gL", "6Ro", "2iA", "j1",
|
||||
"06F", "2Gm", "68K", "5YA", "4DP", "4Q1", "d4f", "82n", "0QR", "6h", "a1E", "bPO", "556", "40v", "0OP", "lb", "BN", "15U", "4ZR", "4O3",
|
||||
"6bA", "4Wb", "0lL", "2Yo", "aR", "t3", "4yN", "6Lm", "6Cl", "42G", "0Ma", "nS", "2Vn", "0cM", "4Xc", "79i", "74Y", "4US", "8ge", "MO",
|
||||
"cc", "1Pp", "bAL", "adN", "0i4", "1lt", "5WZ", "66P", "7LU", "4iW", "0Ry", "5C", "8s", "1Oh", "4dg", "6QD", "7oI", "4JK", "G6", "RW",
|
||||
"2KG", "H7", "4EJ", "64a", "7Nd", "4kf", "0PH", "7r", "1X8", "1MY", "4fV", "4s7", "69Q", "4Hz", "0sT", "Pf", "0mV", "Nd", "5S8", "4Vx",
|
||||
"4xT", "4m5", "22Q", "0Cz", "0NJ", "mx", "7Pf", "41l", "5Ki", "6nk", "CT", "V5", "Y4", "LU", "6aj", "4TI", "4ze", "6OF", "by", "0AK",
|
||||
"2l9", "oI", "4b4", "4wU", "4Yy", "6lZ", "Ae", "0bW", "0So", "4U", "7MC", "4hA", "4Fm", "67F", "3XA", "09K", "0ps", "SA", "aTl", "b5f",
|
||||
"4eq", "6PR", "9e", "8WG", "8XF", "6d", "5o0", "4jp", "707", "65w", "1z2", "1oS", "06J", "Qp", "68G", "4Il", "53i", "6Rc", "2iM", "1LO",
|
||||
"23G", "07", "4yB", "6La", "6bM", "4Wn", "18i", "Or", "BB", "0ap", "c4D", "aEo", "5q2", "40z", "8FD", "ln", "co", "346", "5kR", "6NP",
|
||||
"74U", "bol", "0nq", "MC", "2Vb", "0cA", "4Xo", "6mL", "7SA", "42K", "0Mm", "2xN", "7oE", "4JG", "05a", "2DJ", "2jf", "1Od", "4dk", "6QH",
|
||||
"482", "5yz", "0Ru", "5O", "0i8", "08Q", "4Gw", "5B7", "5M6", "4Hv", "07P", "Pj", "1X4", "1MU", "4fZ", "473", "7Nh", "4kj", "0PD", "sv",
|
||||
"2KK", "1nI", "4EF", "64m", "5Ke", "6ng", "CX", "V9", "0NF", "mt", "7Pj", "4uh", "4xX", "4m9", "1F6", "0Cv", "0mZ", "Nh", "5S4", "4Vt",
|
||||
"4Yu", "6lV", "Ai", "16r", "0Lw", "oE", "4b8", "43Q", "4zi", "6OJ", "bu", "0AG", "Y8", "LY", "6af", "4TE", "4Fi", "67B", "2Hd", "09O",
|
||||
"e8", "4Q", "7MG", "4hE", "4eu", "6PV", "9a", "1Nz", "0pw", "SE", "aTh", "4KY", "4DX", "4Q9", "1z6", "1oW", "0QZ", "rh", "5o4", "4jt",
|
||||
"4gD", "6Rg", "2iI", "j9", "06N", "Qt", "68C", "4Ih", "6bI", "4Wj", "0lD", "Ov", "aZ", "03", "4yF", "6Le", "5q6", "4tv", "0OX", "lj",
|
||||
"BF", "0at", "4ZZ", "6oy", "74Q", "5Ez", "0nu", "MG", "ck", "1Px", "5kV", "6NT", "6Cd", "42O", "0Mi", "2xJ", "2Vf", "0cE", "4Xk", "6mH",
|
||||
"2jb", "8VY", "4do", "6QL", "7oA", "4JC", "05e", "2DN", "d7E", "08U", "4Gs", "5B3", "486", "bSl", "0Rq", "5K", "1X0", "1MQ", "52w", "477",
|
||||
"5M2", "4Hr", "07T", "Pn", "2KO", "1nM", "4EB", "64i", "7Nl", "4kn", "8YX", "7z", "0NB", "mp", "7Pn", "41d", "5Ka", "6nc", "2UM", "14G",
|
||||
"19w", "Nl", "5S0", "4Vp", "bBo", "agm", "1F2", "0Cr", "0Ls", "oA", "ahl", "43U", "4Yq", "6lR", "Am", "16v", "0oo", "2ZL", "6ab", "4TA",
|
||||
"4zm", "6ON", "bq", "0AC", "2VY", "0cz", "4XT", "4M5", "570", "42p", "0MV", "nd", "cT", "v5", "5ki", "6Nk", "74n", "4Ud", "0nJ", "Mx",
|
||||
"By", "0aK", "4Ze", "6oF", "6Aj", "40A", "y4", "lU", "ae", "0BW", "4yy", "581", "4B4", "4WU", "18R", "OI", "06q", "QK", "7lU", "4IW",
|
||||
"53R", "6RX", "0I4", "1Lt", "g6", "rW", "7OI", "4jK", "4Dg", "65L", "2Jj", "1oh", "0pH", "Sz", "7nd", "4Kf", "4eJ", "6Pi", "2kG", "h7",
|
||||
"0ST", "4n", "7Mx", "4hz", "4FV", "4S7", "1x8", "09p", "4zR", "4o3", "bN", "8Hd", "0oP", "Lb", "75t", "604", "4YN", "6lm", "AR", "T3",
|
||||
"0LL", "2yo", "6BA", "43j", "4xc", "agR", "22f", "0CM", "0ma", "NS", "6cl", "4VO", "baL", "aDN", "Cc", "14x", "8Ge", "mO", "7PQ", "4uS",
|
||||
"7NS", "4kQ", "245", "7E", "0k2", "1nr", "coo", "64V", "69f", "4HM", "E0", "PQ", "2hl", "1Mn", "4fa", "6SB", "7Lb", "5yA", "0RN", "5t",
|
||||
"2IA", "J1", "4GL", "66g", "aUM", "b4G", "05Z", "0d3", "8D", "8Vf", "4dP", "459", "574", "42t", "0MR", "0X3", "dln", "17W", "4XP", "4M1",
|
||||
"74j", "5EA", "0nN", "3KL", "cP", "29", "5km", "6No", "6An", "40E", "y0", "lQ", "2Tl", "0aO", "4Za", "6oB", "4B0", "4WQ", "18V", "OM",
|
||||
"aa", "0BS", "bCN", "585", "53V", "axN", "0I0", "1Lp", "06u", "QO", "68x", "4IS", "4Dc", "65H", "2Jn", "1ol", "g2", "rS", "7OM", "4jO",
|
||||
"4eN", "6Pm", "9Z", "h3", "04D", "2Eo", "aTS", "4Kb", "4FR", "4S3", "d6d", "09t", "0SP", "4j", "a3G", "bRM", "0oT", "Lf", "6aY", "4Tz",
|
||||
"4zV", "4o7", "bJ", "0Ax", "0LH", "oz", "6BE", "43n", "4YJ", "6li", "AV", "T7", "0me", "NW", "6ch", "4VK", "4xg", "6MD", "22b", "0CI",
|
||||
"0Ny", "mK", "7PU", "4uW", "5KZ", "6nX", "Cg", "1pt", "0k6", "1nv", "4Ey", "64R", "7NW", "4kU", "241", "7A", "2hh", "1Mj", "4fe", "6SF",
|
||||
"69b", "4HI", "E4", "PU", "2IE", "J5", "4GH", "66c", "7Lf", "4id", "0RJ", "5p", "2jY", "8Vb", "4dT", "4q5", "5O8", "4Jx", "0qV", "Rd",
|
||||
"21E", "25", "5ka", "6Nc", "74f", "4Ul", "0nB", "Mp", "1f2", "0cr", "bbo", "79V", "578", "42x", "395", "nl", "am", "364", "4yq", "589",
|
||||
"76W", "bmn", "0ls", "OA", "Bq", "0aC", "4Zm", "6oN", "6Ab", "40I", "0Oo", "2zL", "0Qm", "6W", "7OA", "4jC", "4Do", "65D", "2Jb", "82Q",
|
||||
"06y", "QC", "68t", "b7d", "4gs", "5b3", "dSM", "8UE", "8ZD", "4f", "5m2", "4hr", "725", "67u", "1x0", "09x", "04H", "Sr", "7nl", "4Kn",
|
||||
"4eB", "6Pa", "9V", "1NM", "4YF", "6le", "AZ", "0bh", "0LD", "ov", "6BI", "43b", "4zZ", "6Oy", "bF", "0At", "0oX", "Lj", "5Q6", "4Tv",
|
||||
"5KV", "6nT", "Ck", "14p", "0Nu", "mG", "7PY", "41S", "4xk", "6MH", "22n", "0CE", "0mi", "2XJ", "6cd", "4VG", "69n", "4HE", "E8", "PY",
|
||||
"2hd", "1Mf", "4fi", "6SJ", "ath", "4kY", "0Pw", "7M", "2Kx", "1nz", "4Eu", "6pV", "5O4", "4Jt", "05R", "Rh", "8L", "1OW", "4dX", "451",
|
||||
"7Lj", "4ih", "0RF", "qt", "2II", "J9", "4GD", "66o", "74b", "4Uh", "0nF", "Mt", "cX", "21", "5ke", "6Ng", "5s4", "4vt", "0MZ", "nh",
|
||||
"1f6", "0cv", "4XX", "4M9", "4B8", "4WY", "0lw", "OE", "ai", "1Rz", "4yu", "6LV", "6Af", "40M", "y8", "lY", "Bu", "0aG", "4Zi", "6oJ",
|
||||
"4Dk", "6qH", "2Jf", "1od", "0Qi", "6S", "7OE", "4jG", "4gw", "5b7", "0I8", "1Lx", "0ru", "QG", "68p", "5Yz", "4FZ", "67q", "1x4", "1mU",
|
||||
"0SX", "4b", "5m6", "4hv", "4eF", "6Pe", "9R", "1NI", "04L", "Sv", "7nh", "4Kj", "8EX", "or", "6BM", "43f", "4YB", "6la", "2WO", "0bl",
|
||||
"8fD", "Ln", "5Q2", "4Tr", "cPL", "aeo", "bB", "0Ap", "0Nq", "mC", "ajn", "41W", "5KR", "6nP", "Co", "14t", "0mm", "2XN", "77I", "4VC",
|
||||
"4xo", "6ML", "22j", "0CA", "3xA", "1Mb", "4fm", "6SN", "69j", "4HA", "07g", "2FL", "d5G", "83O", "4Eq", "64Z", "a0d", "bQn", "0Ps", "7I",
|
||||
"8H", "1OS", "50u", "455", "5O0", "4Jp", "05V", "Rl", "2IM", "08f", "5Wa", "66k", "7Ln", "4il", "0RB", "5x", "Bh", "0aZ", "4Zt", "6oW",
|
||||
"4a9", "40P", "0Ov", "lD", "at", "0BF", "4yh", "6LK", "6bg", "4WD", "Z9", "OX", "2VH", "U8", "4XE", "6mf", "6CJ", "42a", "0MG", "nu",
|
||||
"cE", "1PV", "5kx", "4n8", "5P5", "4Uu", "8gC", "Mi", "04Q", "Sk", "5N7", "4Kw", "51r", "442", "9O", "1NT", "0SE", "pw", "7Mi", "4hk",
|
||||
"4FG", "67l", "2HJ", "09a", "3", "QZ", "68m", "4IF", "4gj", "6RI", "2ig", "1Le", "0Qt", "6N", "7OX", "4jZ", "4Dv", "5A6", "0j9", "1oy",
|
||||
"4xr", "6MQ", "22w", "377", "0mp", "NB", "77T", "blm", "5KO", "6nM", "Cr", "14i", "0Nl", "3kn", "ajs", "41J", "4zC", "aer", "20F", "36",
|
||||
"0oA", "Ls", "6aL", "4To", "bcl", "78U", "AC", "0bq", "386", "oo", "5r3", "4ws", "5l1", "4iq", "9Kf", "5e", "1y3", "1lR", "736", "66v",
|
||||
"7oo", "4Jm", "05K", "Rq", "8U", "1ON", "4dA", "6Qb", "7NB", "bQs", "0Pn", "7T", "2Ka", "1nc", "4El", "64G", "69w", "b6g", "07z", "1v2",
|
||||
"dRN", "8TF", "4fp", "5c0", "akm", "40T", "0Or", "1J2", "Bl", "15w", "4Zp", "6oS", "6bc", "5Ga", "0ln", "2YM", "ap", "0BB", "4yl", "6LO",
|
||||
"6CN", "42e", "0MC", "nq", "2VL", "0co", "4XA", "6mb", "5P1", "4Uq", "8gG", "Mm", "cA", "1PR", "bAn", "adl", "51v", "446", "9K", "1NP",
|
||||
"04U", "So", "5N3", "4Ks", "4FC", "67h", "2HN", "09e", "0SA", "ps", "7Mm", "4ho", "4gn", "6RM", "2ic", "1La", "7", "2GO", "68i", "4IB",
|
||||
"4Dr", "5A2", "d4D", "82L", "0Qp", "6J", "a1g", "bPm", "0mt", "NF", "6cy", "4VZ", "4xv", "6MU", "0V9", "0CX", "0Nh", "mZ", "7PD", "41N",
|
||||
"5KK", "6nI", "Cv", "14m", "0oE", "Lw", "6aH", "4Tk", "4zG", "6Od", "20B", "32", "0LY", "ok", "5r7", "4ww", "5Iz", "6lx", "AG", "0bu",
|
||||
"1y7", "1lV", "4GY", "4R8", "5l5", "4iu", "1Bz", "5a", "8Q", "i8", "4dE", "6Qf", "7ok", "4Ji", "05O", "Ru", "2Ke", "1ng", "4Eh", "64C",
|
||||
"7NF", "4kD", "f9", "7P", "2hy", "3m9", "4ft", "5c4", "69s", "4HX", "0sv", "PD", "23e", "0BN", "5iA", "6LC", "6bo", "4WL", "Z1", "OP",
|
||||
"0t3", "0aR", "c4f", "aEM", "4a1", "40X", "8Ff", "lL", "cM", "8Ig", "5kp", "4n0", "74w", "617", "0nS", "Ma", "3Fa", "U0", "4XM", "6mn",
|
||||
"6CB", "42i", "0MO", "2xl", "0SM", "4w", "7Ma", "4hc", "4FO", "67d", "2HB", "K2", "04Y", "Sc", "aTN", "b5D", "4eS", "4p2", "9G", "8We",
|
||||
"256", "6F", "7OP", "4jR", "cnl", "65U", "0j1", "1oq", "D3", "QR", "68e", "4IN", "4gb", "6RA", "2io", "1Lm", "5KG", "6nE", "Cz", "14a",
|
||||
"x7", "mV", "7PH", "41B", "4xz", "592", "0V5", "0CT", "0mx", "NJ", "4C7", "4VV", "4YW", "4L6", "AK", "0by", "0LU", "og", "563", "43s",
|
||||
"4zK", "6Oh", "bW", "w6", "0oI", "2Zj", "6aD", "4Tg", "7og", "4Je", "05C", "Ry", "2jD", "i4", "4dI", "6Qj", "5l9", "4iy", "0RW", "5m",
|
||||
"2IX", "08s", "4GU", "4R4", "7mV", "4HT", "07r", "PH", "0H7", "1Mw", "4fx", "5c8", "7NJ", "4kH", "f5", "sT", "2Ki", "1nk", "4Ed", "64O",
|
||||
"6bk", "4WH", "Z5", "OT", "ax", "0BJ", "4yd", "6LG", "4a5", "4tT", "0Oz", "lH", "Bd", "0aV", "4Zx", "aEI", "5P9", "4Uy", "0nW", "Me",
|
||||
"cI", "1PZ", "5kt", "4n4", "6CF", "42m", "0MK", "ny", "2VD", "U4", "4XI", "6mj", "4FK", "6sh", "2HF", "K6", "0SI", "4s", "7Me", "4hg",
|
||||
"4eW", "4p6", "9C", "1NX", "0pU", "Sg", "7ny", "6k9", "4Dz", "65Q", "0j5", "1ou", "0Qx", "6B", "7OT", "4jV", "4gf", "6RE", "2ik", "1Li",
|
||||
"D7", "QV", "68a", "4IJ", "x3", "mR", "7PL", "41F", "5KC", "6nA", "2Uo", "14e", "19U", "NN", "4C3", "4VR", "bBM", "596", "0V1", "0CP",
|
||||
"0LQ", "oc", "567", "43w", "4YS", "4L2", "AO", "16T", "0oM", "2Zn", "75i", "4Tc", "4zO", "6Ol", "bS", "w2", "8Y", "i0", "4dM", "6Qn",
|
||||
"7oc", "4Ja", "05G", "2Dl", "d7g", "08w", "4GQ", "4R0", "a2D", "bSN", "0RS", "5i", "0H3", "1Ms", "52U", "ayM", "7mR", "4HP", "07v", "PL",
|
||||
"2Km", "1no", "5UA", "64K", "7NN", "4kL", "f1", "7X", "5nw", "4k7", "fJ", "0Ex", "0kT", "Hf", "6eY", "4Pz", "5Mk", "6hi", "EV", "P7",
|
||||
"0HH", "kz", "6FE", "47n", "48o", "6ID", "26b", "0GI", "0ie", "JW", "6gh", "4RK", "5OZ", "6jX", "Gg", "0dU", "0Jy", "iK", "4d6", "4qW",
|
||||
"4z4", "4oU", "1DZ", "3A", "Ye", "0zW", "4Ay", "5D9", "6yj", "4LI", "A4", "TU", "zy", "0YK", "4be", "6WF", "6XG", "4md", "0VJ", "1p",
|
||||
"2ME", "N5", "4CH", "62c", "5K8", "4Nx", "0uV", "Vd", "xH", "8Rb", "5pu", "4u5", "D", "13W", "5Lq", "4I1", "534", "46t", "0IR", "28y",
|
||||
"gP", "69", "5om", "6Jo", "6dC", "5AA", "0jN", "3OL", "2Pl", "0eO", "aT1", "6kB", "6En", "44E", "98", "hQ", "ea", "0FS", "49u", "abL",
|
||||
"4F0", "4SQ", "8ag", "KM", "02u", "UO", "4X2", "4MS", "57V", "a8F", "0M0", "0XQ", "c2", "vS", "7KM", "4nO", "5PB", "61H", "2Nn", "1kl",
|
||||
"00D", "2Ao", "6zA", "4Ob", "4aN", "6Tm", "yR", "l3", "0WP", "0j", "a7G", "58W", "4BR", "4W3", "ZN", "84l", "0kP", "Hb", "71t", "644",
|
||||
"5ns", "4k3", "fN", "8Ld", "0HL", "29g", "6FA", "47j", "5Mo", "6hm", "ER", "P3", "0ia", "JS", "6gl", "4RO", "48k", "7Ya", "26f", "0GM",
|
||||
"8Ce", "iO", "4d2", "4qS", "beL", "hYw", "Gc", "0dQ", "Ya", "0zS", "cko", "60V", "4z0", "4oQ", "205", "3E", "2ll", "0YO", "4ba", "6WB",
|
||||
"6yn", "4LM", "A0", "TQ", "2MA", "N1", "4CL", "62g", "6XC", "59I", "0VN", "1t", "xL", "8Rf", "54y", "419", "aQM", "b0G", "01Z", "3PP",
|
||||
"530", "46p", "0IV", "jd", "DH", "0gz", "5Lu", "4I5", "6dG", "4Qd", "0jJ", "Ix", "gT", "r5", "5oi", "6Jk", "6Ej", "44A", "0Kg", "hU",
|
||||
"Fy", "0eK", "5ND", "6kF", "4F4", "4SU", "1xZ", "KI", "ee", "0FW", "49q", "5x9", "57R", "6VX", "0M4", "0XU", "02q", "UK", "4X6", "4MW",
|
||||
"5PF", "61L", "2Nj", "1kh", "c6", "vW", "7KI", "4nK", "4aJ", "6Ti", "yV", "l7", "0tH", "Wz", "6zE", "4Of", "4BV", "4W7", "ZJ", "0yx",
|
||||
"0WT", "0n", "6YY", "4lz", "5Mc", "6ha", "2SO", "0fl", "1Xa", "kr", "6FM", "47f", "bDm", "aao", "fB", "0Ep", "8bD", "Hn", "5U2", "4Pr",
|
||||
"5OR", "5Z3", "Go", "10t", "0Jq", "iC", "ann", "45W", "48g", "6IL", "ds", "0GA", "0im", "3Lo", "73I", "4RC", "6yb", "4LA", "03g", "2BL",
|
||||
"zq", "0YC", "4bm", "6WN", "a4d", "bUn", "0Ts", "3I", "Ym", "87O", "4Aq", "5D1", "5K0", "4Np", "01V", "Vl", "2nQ", "1KS", "54u", "415",
|
||||
"6XO", "4ml", "0VB", "1x", "2MM", "0xn", "5Sa", "62k", "gX", "61", "5oe", "6Jg", "6dK", "4Qh", "0jF", "It", "L", "0gv", "5Ly", "4I9",
|
||||
"5w4", "4rt", "0IZ", "jh", "ei", "1Vz", "5mT", "5x5", "4F8", "4SY", "0hw", "KE", "Fu", "0eG", "5NH", "6kJ", "6Ef", "44M", "90", "hY",
|
||||
"0Ui", "2S", "7KE", "4nG", "5PJ", "6uH", "Xw", "1kd", "0vu", "UG", "6xx", "790", "4cw", "5f7", "0M8", "0XY", "0WX", "0b", "5i6", "4lv",
|
||||
"4BZ", "63q", "ZF", "0yt", "00L", "Wv", "6zI", "4Oj", "4aF", "6Te", "yZ", "0Zh", "0HD", "kv", "6FI", "47b", "5Mg", "6he", "EZ", "0fh",
|
||||
"0kX", "Hj", "5U6", "4Pv", "7N9", "6Ky", "fF", "0Et", "0Ju", "iG", "6Dx", "45S", "5OV", "5Z7", "Gk", "0dY", "0ii", "3Lk", "6gd", "4RG",
|
||||
"48c", "6IH", "dw", "0GE", "zu", "0YG", "4bi", "6WJ", "6yf", "4LE", "A8", "TY", "Yi", "1jz", "4Au", "5D5", "4z8", "4oY", "0Tw", "3M",
|
||||
"xD", "1KW", "54q", "411", "5K4", "4Nt", "01R", "Vh", "2MI", "N9", "4CD", "62o", "6XK", "4mh", "0VF", "ut", "6dO", "4Ql", "0jB", "Ip",
|
||||
"25E", "65", "5oa", "6Jc", "538", "46x", "9Pg", "jl", "H", "0gr", "bfo", "aCm", "72W", "bin", "0hs", "KA", "em", "324", "49y", "5x1",
|
||||
"6Eb", "44I", "94", "3nm", "Fq", "0eC", "5NL", "6kN", "5PN", "61D", "Xs", "86Q", "0Um", "2W", "7KA", "4nC", "4cs", "5f3", "39W", "8QE",
|
||||
"02y", "UC", "aRn", "794", "765", "63u", "ZB", "0yp", "9Ne", "0f", "5i2", "4lr", "4aB", "6Ta", "2oO", "0Zl", "00H", "Wr", "6zM", "4On",
|
||||
"5lW", "5y6", "dj", "0GX", "0it", "JF", "6gy", "4RZ", "5OK", "6jI", "Gv", "0dD", "83", "iZ", "6De", "45N", "5nf", "6Kd", "24B", "72",
|
||||
"0kE", "Hw", "6eH", "4Pk", "5Mz", "6hx", "EG", "0fu", "0HY", "kk", "5v7", "4sw", "5h5", "4mu", "1Fz", "1a", "2MT", "0xw", "4CY", "4V8",
|
||||
"7kk", "4Ni", "01O", "Vu", "xY", "m8", "54l", "6Uf", "6Zg", "4oD", "b9", "3P", "Yt", "0zF", "4Ah", "60C", "4Y9", "4LX", "0wv", "TD",
|
||||
"zh", "0YZ", "4bt", "5g4", "Fl", "11w", "5NQ", "6kS", "aom", "44T", "0Kr", "1N2", "ep", "0FB", "49d", "6HO", "6fc", "5Ca", "0hn", "3Ml",
|
||||
"U", "0go", "bfr", "6ib", "6GN", "46e", "0IC", "jq", "gA", "0Ds", "bEn", "hyU", "5T1", "4Qq", "8cG", "Im", "00U", "Wo", "5J3", "4Os",
|
||||
"55v", "406", "yC", "0Zq", "0WA", "ts", "6YL", "4lo", "4BC", "63h", "2LN", "0ym", "02d", "2CO", "6xa", "4MB", "4cn", "6VM", "2mc", "1Ha",
|
||||
"0Up", "2J", "a5g", "bTm", "5PS", "5E2", "Xn", "86L", "0ip", "JB", "73T", "bhm", "48z", "5y2", "dn", "337", "87", "3on", "6Da", "45J",
|
||||
"5OO", "6jM", "Gr", "10i", "0kA", "Hs", "6eL", "4Po", "5nb", "aar", "24F", "76", "8AE", "ko", "5v3", "4ss", "bgl", "aBn", "EC", "0fq",
|
||||
"2MP", "0xs", "776", "62v", "5h1", "4mq", "9Of", "1e", "2nL", "1KN", "54h", "6Ub", "7ko", "4Nm", "01K", "Vq", "Yp", "0zB", "4Al", "60G",
|
||||
"6Zc", "bUs", "0Tn", "3T", "zl", "8PF", "4bp", "5g0", "aSm", "787", "03z", "1r2", "4e9", "44P", "0Kv", "hD", "Fh", "0eZ", "5NU", "6kW",
|
||||
"6fg", "4SD", "0hj", "KX", "et", "0FF", "5mI", "6HK", "6GJ", "46a", "0IG", "ju", "Q", "Q8", "5Ld", "6if", "5T5", "4Qu", "1zz", "Ii",
|
||||
"gE", "0Dw", "5ox", "4j8", "55r", "402", "yG", "0Zu", "00Q", "Wk", "5J7", "4Ow", "4BG", "63l", "2LJ", "0yi", "0WE", "tw", "6YH", "4lk",
|
||||
"4cj", "6VI", "2mg", "0XD", "0vh", "UZ", "6xe", "4MF", "5PW", "5E6", "Xj", "1ky", "0Ut", "2N", "7KX", "4nZ", "5OC", "6jA", "2Qo", "0dL",
|
||||
"1ZA", "iR", "6Dm", "45F", "48v", "acO", "db", "0GP", "94M", "JN", "4G3", "4RR", "5Mr", "4H2", "EO", "12T", "0HQ", "kc", "527", "47w",
|
||||
"5nn", "6Kl", "fS", "s2", "0kM", "3NO", "71i", "4Pc", "7kc", "4Na", "01G", "3PM", "xQ", "m0", "54d", "6Un", "a6D", "59T", "0VS", "1i",
|
||||
"197", "85o", "4CQ", "4V0", "4Y1", "4LP", "03v", "TL", "0L3", "0YR", "56U", "a9E", "6Zo", "4oL", "b1", "3X", "2Om", "0zN", "5QA", "60K",
|
||||
"ex", "0FJ", "49l", "6HG", "6fk", "4SH", "0hf", "KT", "Fd", "0eV", "5NY", "aAI", "4e5", "4pT", "0Kz", "hH", "gI", "1TZ", "5ot", "4j4",
|
||||
"5T9", "4Qy", "0jW", "Ie", "DU", "Q4", "5Lh", "6ij", "6GF", "46m", "0IK", "jy", "0WI", "0s", "6YD", "4lg", "4BK", "6wh", "ZW", "O6",
|
||||
"0tU", "Wg", "6zX", "6o9", "4aW", "4t6", "yK", "0Zy", "0Ux", "2B", "7KT", "4nV", "bzI", "61Q", "Xf", "1ku", "02l", "UV", "6xi", "4MJ",
|
||||
"4cf", "6VE", "2mk", "0XH", "0Jd", "iV", "6Di", "45B", "5OG", "6jE", "Gz", "0dH", "0ix", "JJ", "4G7", "4RV", "48r", "6IY", "df", "0GT",
|
||||
"0HU", "kg", "523", "47s", "5Mv", "4H6", "EK", "0fy", "0kI", "3NK", "6eD", "4Pg", "5nj", "6Kh", "fW", "s6", "xU", "m4", "5ph", "6Uj",
|
||||
"7kg", "4Ne", "01C", "Vy", "193", "1hZ", "4CU", "4V4", "5h9", "4my", "0VW", "1m", "zd", "0YV", "4bx", "5g8", "4Y5", "4LT", "03r", "TH",
|
||||
"Yx", "0zJ", "4Ad", "60O", "6Zk", "4oH", "b5", "wT", "6fo", "4SL", "0hb", "KP", "27e", "0FN", "49h", "6HC", "4e1", "44X", "8Bf", "hL",
|
||||
"0p3", "0eR", "bdO", "aAM", "70w", "657", "0jS", "Ia", "gM", "8Mg", "5op", "4j0", "6GB", "46i", "0IO", "28d", "Y", "Q0", "5Ll", "6in",
|
||||
"4BO", "63d", "ZS", "O2", "0WM", "0w", "7Ia", "4lc", "4aS", "4t2", "yO", "8Se", "00Y", "Wc", "aPN", "b1D", "bzM", "61U", "Xb", "1kq",
|
||||
"216", "2F", "7KP", "4nR", "4cb", "6VA", "2mo", "0XL", "02h", "UR", "6xm", "4MN", "5j7", "4ow", "0TY", "3c", "YG", "0zu", "5Qz", "60p",
|
||||
"6yH", "4Lk", "03M", "Tw", "2lJ", "0Yi", "4bG", "6Wd", "6Xe", "4mF", "0Vh", "1R", "2Mg", "0xD", "4Cj", "62A", "7kX", "4NZ", "0ut", "VF",
|
||||
"xj", "1Ky", "5pW", "5e6", "5nU", "6KW", "fh", "0EZ", "0kv", "HD", "4E9", "4PX", "5MI", "6hK", "Et", "0fF", "0Hj", "kX", "6Fg", "47L",
|
||||
"48M", "6If", "dY", "50", "0iG", "Ju", "6gJ", "4Ri", "5Ox", "4J8", "GE", "0dw", "1Zz", "ii", "5t5", "4qu", "02W", "Um", "5H1", "4Mq",
|
||||
"57t", "424", "2mP", "0Xs", "0UC", "2y", "7Ko", "4nm", "bzr", "61j", "2NL", "1kN", "00f", "2AM", "6zc", "bus", "4al", "6TO", "yp", "0ZB",
|
||||
"0Wr", "0H", "a7e", "58u", "4Bp", "5G0", "Zl", "84N", "f", "13u", "5LS", "5Y2", "amo", "46V", "0Ip", "jB", "gr", "1Ta", "5oO", "6JM",
|
||||
"6da", "4QB", "0jl", "3On", "2PN", "0em", "5Nb", "aAr", "6EL", "44g", "0KA", "hs", "eC", "0Fq", "49W", "abn", "5V3", "4Ss", "8aE", "Ko",
|
||||
"YC", "0zq", "754", "60t", "5j3", "4os", "9Md", "3g", "2lN", "0Ym", "4bC", "7GA", "6yL", "4Lo", "03I", "Ts", "2Mc", "1ha", "4Cn", "62E",
|
||||
"6Xa", "4mB", "0Vl", "1V", "xn", "8RD", "5pS", "5e2", "aQo", "b0e", "01x", "VB", "0kr", "1n2", "71V", "bjo", "5nQ", "6KS", "fl", "315",
|
||||
"0Hn", "29E", "6Fc", "47H", "5MM", "6hO", "Ep", "0fB", "0iC", "Jq", "6gN", "4Rm", "48I", "6Ib", "26D", "54", "8CG", "im", "509", "45y",
|
||||
"ben", "hYU", "GA", "0ds", "4cY", "420", "2mT", "0Xw", "02S", "Ui", "5H5", "4Mu", "5Pd", "61n", "XY", "M8", "0UG", "vu", "7Kk", "4ni",
|
||||
"4ah", "6TK", "yt", "0ZF", "B9", "WX", "6zg", "4OD", "4Bt", "5G4", "Zh", "0yZ", "0Wv", "0L", "4y9", "4lX", "6Gy", "46R", "0It", "jF",
|
||||
"b", "0gX", "5LW", "5Y6", "6de", "4QF", "0jh", "IZ", "gv", "0DD", "5oK", "6JI", "6EH", "44c", "0KE", "hw", "2PJ", "0ei", "5Nf", "6kd",
|
||||
"5V7", "4Sw", "0hY", "Kk", "eG", "0Fu", "49S", "6Hx", "7ia", "4Lc", "03E", "2Bn", "zS", "o2", "4bO", "6Wl", "a4F", "bUL", "0TQ", "3k",
|
||||
"YO", "87m", "4AS", "4T2", "7kP", "4NR", "01t", "VN", "xb", "1Kq", "54W", "hfv", "6Xm", "4mN", "1FA", "1Z", "2Mo", "0xL", "4Cb", "62I",
|
||||
"5MA", "6hC", "2Sm", "0fN", "0Hb", "kP", "6Fo", "47D", "bDO", "aaM", "0P3", "0ER", "8bf", "HL", "4E1", "4PP", "5Op", "4J0", "GM", "10V",
|
||||
"0JS", "ia", "505", "45u", "48E", "6In", "dQ", "58", "0iO", "3LM", "6gB", "4Ra", "0UK", "2q", "7Kg", "4ne", "5Ph", "61b", "XU", "M4",
|
||||
"0vW", "Ue", "5H9", "4My", "4cU", "4v4", "2mX", "1HZ", "0Wz", "tH", "4y5", "4lT", "4Bx", "5G8", "Zd", "0yV", "B5", "WT", "6zk", "4OH",
|
||||
"4ad", "6TG", "yx", "0ZJ", "gz", "0DH", "5oG", "6JE", "6di", "4QJ", "0jd", "IV", "n", "0gT", "680", "6iY", "4g7", "4rV", "0Ix", "jJ",
|
||||
"eK", "0Fy", "5mv", "4h6", "6fX", "5CZ", "0hU", "Kg", "FW", "S6", "5Nj", "6kh", "6ED", "44o", "0KI", "3nK", "zW", "o6", "4bK", "6Wh",
|
||||
"6yD", "4Lg", "03A", "2Bj", "YK", "0zy", "4AW", "4T6", "6ZX", "6O9", "0TU", "3o", "xf", "1Ku", "54S", "6UY", "7kT", "4NV", "01p", "VJ",
|
||||
"2Mk", "0xH", "4Cf", "62M", "6Xi", "4mJ", "0Vd", "uV", "0Hf", "kT", "6Fk", "4sH", "5ME", "6hG", "Ex", "0fJ", "0kz", "HH", "4E5", "4PT",
|
||||
"5nY", "aaI", "fd", "0EV", "0JW", "ie", "501", "45q", "5Ot", "4J4", "GI", "10R", "0iK", "Jy", "6gF", "4Re", "48A", "6Ij", "dU", "q4",
|
||||
"5Pl", "61f", "XQ", "M0", "0UO", "2u", "7Kc", "4na", "4cQ", "428", "39u", "8Qg", "0vS", "Ua", "aRL", "b3F", "bxO", "63W", "0l3", "0yR",
|
||||
"234", "0D", "4y1", "4lP", "55I", "6TC", "2om", "0ZN", "B1", "WP", "6zo", "4OL", "6dm", "4QN", "1zA", "IR", "25g", "0DL", "5oC", "6JA",
|
||||
"4g3", "46Z", "9PE", "jN", "j", "0gP", "684", "aCO", "72u", "675", "0hQ", "Kc", "eO", "8Oe", "5mr", "4h2", "7Ua", "44k", "0KM", "3nO",
|
||||
"FS", "S2", "5Nn", "6kl", "4x6", "4mW", "0Vy", "1C", "0m4", "0xU", "5SZ", "62P", "7kI", "4NK", "C6", "VW", "2nj", "1Kh", "54N", "6UD",
|
||||
"6ZE", "4of", "0TH", "3r", "YV", "L7", "4AJ", "60a", "6yY", "4Lz", "0wT", "Tf", "zJ", "0Yx", "4bV", "4w7", "5lu", "4i5", "dH", "0Gz",
|
||||
"0iV", "Jd", "5W8", "4Rx", "5Oi", "6jk", "GT", "R5", "0JJ", "ix", "6DG", "45l", "5nD", "6KF", "fy", "0EK", "0kg", "HU", "6ej", "4PI",
|
||||
"5MX", "5X9", "Ee", "0fW", "1XZ", "kI", "4f4", "4sU", "00w", "WM", "4Z0", "4OQ", "55T", "hgu", "ya", "0ZS", "a0", "0Y", "6Yn", "4lM",
|
||||
"4Ba", "63J", "2Ll", "0yO", "02F", "2Cm", "6xC", "aG0", "4cL", "6Vo", "2mA", "n1", "0UR", "2h", "a5E", "bTO", "5Pq", "4U1", "XL", "86n",
|
||||
"FN", "11U", "5Ns", "4K3", "516", "44v", "0KP", "hb", "eR", "p3", "49F", "6Hm", "6fA", "4Sb", "0hL", "3MN", "w", "0gM", "5LB", "7ya",
|
||||
"6Gl", "46G", "0Ia", "jS", "gc", "0DQ", "bEL", "hyw", "4D2", "4QS", "8ce", "IO", "0m0", "0xQ", "byL", "62T", "4x2", "4mS", "227", "1G",
|
||||
"2nn", "1Kl", "54J", "7Ea", "7kM", "4NO", "C2", "VS", "YR", "L3", "4AN", "60e", "6ZA", "4ob", "0TL", "3v", "zN", "8Pd", "4bR", "4w3",
|
||||
"aSO", "b2E", "03X", "Tb", "0iR", "3LP", "73v", "666", "48X", "4i1", "dL", "8Nf", "0JN", "3oL", "6DC", "45h", "5Om", "6jo", "GP", "R1",
|
||||
"0kc", "HQ", "6en", "4PM", "a09", "6KB", "24d", "0EO", "8Ag", "kM", "4f0", "47Y", "697", "aBL", "Ea", "0fS", "4ay", "5d9", "ye", "0ZW",
|
||||
"00s", "WI", "4Z4", "4OU", "4Be", "63N", "Zy", "0yK", "a4", "tU", "6Yj", "4lI", "4cH", "6Vk", "2mE", "n5", "02B", "Ux", "6xG", "4Md",
|
||||
"5Pu", "4U5", "XH", "86j", "0UV", "2l", "5k8", "4nx", "512", "44r", "0KT", "hf", "FJ", "0ex", "5Nw", "4K7", "6fE", "4Sf", "0hH", "Kz",
|
||||
"eV", "p7", "49B", "6Hi", "6Gh", "46C", "0Ie", "jW", "s", "0gI", "5LF", "6iD", "4D6", "4QW", "0jy", "IK", "gg", "0DU", "5oZ", "6JX",
|
||||
"7kA", "4NC", "01e", "3Po", "xs", "8RY", "54F", "6UL", "a6f", "59v", "0Vq", "1K", "d3E", "85M", "4Cs", "5F3", "5I2", "4Lr", "03T", "Tn",
|
||||
"zB", "0Yp", "56w", "437", "6ZM", "4on", "1Da", "3z", "2OO", "0zl", "4AB", "60i", "5Oa", "6jc", "2QM", "0dn", "0JB", "ip", "6DO", "45d",
|
||||
"48T", "acm", "1B2", "0Gr", "94o", "Jl", "5W0", "4Rp", "5MP", "5X1", "Em", "12v", "0Hs", "kA", "all", "47U", "5nL", "6KN", "fq", "0EC",
|
||||
"0ko", "3Nm", "6eb", "4PA", "a8", "0Q", "6Yf", "4lE", "4Bi", "63B", "Zu", "0yG", "0tw", "WE", "4Z8", "4OY", "4au", "5d5", "yi", "1Jz",
|
||||
"0UZ", "vh", "5k4", "4nt", "5Py", "4U9", "XD", "1kW", "02N", "Ut", "6xK", "4Mh", "4cD", "6Vg", "2mI", "n9", "eZ", "43", "49N", "6He",
|
||||
"6fI", "4Sj", "0hD", "Kv", "FF", "0et", "7n9", "6ky", "5u6", "4pv", "0KX", "hj", "gk", "0DY", "5oV", "5z7", "6dx", "5Az", "0ju", "IG",
|
||||
"Dw", "0gE", "5LJ", "6iH", "6Gd", "46O", "0Ii", "28B", "xw", "1Kd", "54B", "6UH", "7kE", "4NG", "01a", "3Pk", "0m8", "0xY", "4Cw", "5F7",
|
||||
"6Xx", "59r", "0Vu", "1O", "zF", "0Yt", "4bZ", "433", "5I6", "4Lv", "03P", "Tj", "YZ", "0zh", "4AF", "60m", "6ZI", "4oj", "0TD", "wv",
|
||||
"0JF", "it", "6DK", "4qh", "5Oe", "6jg", "GX", "R9", "0iZ", "Jh", "5W4", "4Rt", "48P", "4i9", "dD", "0Gv", "0Hw", "kE", "4f8", "47Q",
|
||||
"5MT", "5X5", "Ei", "12r", "0kk", "HY", "6ef", "4PE", "5nH", "6KJ", "fu", "0EG", "4Bm", "63F", "Zq", "0yC", "0Wo", "0U", "6Yb", "4lA",
|
||||
"4aq", "5d1", "ym", "8SG", "0ts", "WA", "aPl", "b1f", "747", "61w", "2NQ", "1kS", "9Lg", "2d", "5k0", "4np", "57i", "6Vc", "2mM", "0Xn",
|
||||
"02J", "Up", "6xO", "4Ml", "6fM", "4Sn", "1xa", "Kr", "27G", "47", "49J", "6Ha", "5u2", "44z", "8BD", "hn", "FB", "0ep", "bdm", "aAo",
|
||||
"70U", "bkl", "0jq", "IC", "go", "306", "5oR", "5z3", "7WA", "46K", "0Im", "28F", "Ds", "0gA", "5LN", "6iL", "0cY", "020", "6mT", "4Xw",
|
||||
"42S", "6Cx", "nG", "0Mu", "1Pd", "cw", "6NH", "5kJ", "4UG", "74M", "3Kk", "0ni", "0ah", "BZ", "6oe", "4ZF", "40b", "6AI", "lv", "0OD",
|
||||
"0Bt", "aF", "6Ly", "4yZ", "4Wv", "5R6", "Oj", "0lX", "Qh", "06R", "4It", "5L4", "461", "4gX", "1LW", "1Y6", "rt", "0QF", "4jh", "7Oj",
|
||||
"65o", "4DD", "I9", "2JI", "SY", "F8", "4KE", "7nG", "6PJ", "4ei", "1Nf", "2kd", "4M", "0Sw", "4hY", "490", "5C5", "4Fu", "09S", "2Hx",
|
||||
"6OR", "4zq", "354", "bm", "LA", "0os", "bnn", "75W", "6lN", "4Ym", "0bC", "Aq", "2yL", "0Lo", "43I", "6Bb", "6Mc", "5ha", "15", "22E",
|
||||
"Np", "0mB", "4Vl", "6cO", "aDm", "bao", "1pS", "1e2", "ml", "8GF", "41x", "548", "4kr", "5n2", "7f", "8YD", "1nQ", "2KS", "64u", "715",
|
||||
"4Hn", "69E", "Pr", "07H", "1MM", "2hO", "6Sa", "4fB", "4iC", "7LA", "5W", "0Rm", "08I", "2Ib", "66D", "4Go", "b4d", "aUn", "RC", "05y",
|
||||
"8VE", "8g", "5a3", "4ds", "42W", "ain", "nC", "0Mq", "17t", "024", "6mP", "4Xs", "4UC", "74I", "3Ko", "0nm", "8IY", "cs", "6NL", "5kN",
|
||||
"40f", "6AM", "lr", "8FX", "0al", "2TO", "6oa", "4ZB", "4Wr", "5R2", "On", "18u", "0Bp", "aB", "afo", "bCm", "465", "53u", "1LS", "1Y2",
|
||||
"Ql", "06V", "4Ip", "5L0", "65k", "5Ta", "1oO", "2JM", "6x", "0QB", "4jl", "7On", "6PN", "4em", "1Nb", "9y", "2EL", "04g", "4KA", "7nC",
|
||||
"5C1", "4Fq", "09W", "d6G", "4I", "0Ss", "bRn", "494", "LE", "0ow", "4TY", "4A8", "6OV", "4zu", "1Qz", "bi", "oY", "z8", "43M", "6Bf",
|
||||
"6lJ", "4Yi", "0bG", "Au", "Nt", "0mF", "4Vh", "6cK", "6Mg", "4xD", "11", "22A", "mh", "0NZ", "4ut", "5p4", "4N9", "5Ky", "1pW", "CD",
|
||||
"1nU", "2KW", "64q", "4EZ", "4kv", "5n6", "7b", "0PX", "1MI", "2hK", "6Se", "4fF", "4Hj", "69A", "Pv", "07L", "08M", "2If", "6rH", "4Gk",
|
||||
"4iG", "7LE", "5S", "0Ri", "1Ox", "8c", "5a7", "4dw", "5Zz", "7oY", "RG", "0qu", "1Pl", "21f", "adR", "5kB", "4UO", "74E", "MS", "X2",
|
||||
"0cQ", "028", "79u", "bbL", "4vS", "4c2", "nO", "8De", "8Kd", "aN", "4l3", "4yR", "634", "76t", "Ob", "0lP", "W3", "BR", "6om", "4ZN",
|
||||
"40j", "6AA", "2zo", "0OL", "6t", "0QN", "5zA", "7Ob", "65g", "4DL", "I1", "2JA", "0g3", "06Z", "b7G", "68W", "469", "4gP", "284", "dSn",
|
||||
"4E", "275", "4hQ", "498", "67V", "b8F", "1mr", "0h2", "SQ", "F0", "4KM", "7nO", "6PB", "4ea", "1Nn", "9u", "6lF", "4Ye", "0bK", "Ay",
|
||||
"oU", "z4", "43A", "6Bj", "6OZ", "4zy", "0AW", "be", "LI", "2O9", "4TU", "4A4", "4N5", "5Ku", "14S", "CH", "md", "0NV", "41p", "540",
|
||||
"6Mk", "4xH", "u5", "22M", "Nx", "0mJ", "4Vd", "6cG", "4Hf", "69M", "Pz", "0sH", "k7", "2hG", "6Si", "4fJ", "4kz", "7Nx", "7n", "0PT",
|
||||
"1nY", "dqh", "4P7", "4EV", "4JW", "7oU", "RK", "05q", "1Ot", "8o", "6QX", "50R", "4iK", "7LI", "qW", "d6", "08A", "2Ij", "66L", "4Gg",
|
||||
"4UK", "74A", "MW", "X6", "1Ph", "21b", "6ND", "5kF", "4vW", "4c6", "nK", "0My", "0cU", "0v4", "6mX", "5HZ", "4Wz", "6bY", "Of", "0lT",
|
||||
"0Bx", "aJ", "4l7", "4yV", "40n", "6AE", "lz", "0OH", "W7", "BV", "6oi", "4ZJ", "65c", "4DH", "I5", "2JE", "6p", "0QJ", "4jd", "7Of",
|
||||
"4r5", "4gT", "280", "2iY", "Qd", "0rV", "4Ix", "5L8", "5C9", "4Fy", "1mv", "0h6", "4A", "1CZ", "4hU", "7MW", "6PF", "4ee", "1Nj", "9q",
|
||||
"SU", "F4", "4KI", "7nK", "oQ", "z0", "43E", "6Bn", "6lB", "4Ya", "0bO", "2Wl", "LM", "8fg", "4TQ", "4A0", "aeL", "cPo", "0AS", "ba",
|
||||
"3kP", "0NR", "41t", "544", "4N1", "5Kq", "14W", "CL", "2Xm", "0mN", "5FA", "6cC", "6Mo", "4xL", "19", "22I", "k3", "2hC", "6Sm", "4fN",
|
||||
"4Hb", "69I", "2Fo", "07D", "83l", "d5d", "4P3", "4ER", "bQM", "a0G", "7j", "0PP", "1Op", "8k", "hbw", "50V", "4JS", "7oQ", "RO", "05u",
|
||||
"08E", "2In", "66H", "4Gc", "4iO", "7LM", "qS", "d2", "0ay", "BK", "4O6", "4ZW", "40s", "553", "lg", "0OU", "t6", "aW", "6Lh", "4yK",
|
||||
"4Wg", "6bD", "2Yj", "0lI", "0cH", "2Vk", "6mE", "4Xf", "42B", "6Ci", "nV", "0Md", "1Pu", "cf", "6NY", "bAI", "4UV", "7pT", "MJ", "0nx",
|
||||
"SH", "04r", "4KT", "7nV", "azI", "4ex", "1Nw", "9l", "pT", "e5", "4hH", "7MJ", "67O", "4Fd", "09B", "2Hi", "Qy", "06C", "4Ie", "68N",
|
||||
"6Rj", "4gI", "j4", "2iD", "6m", "0QW", "4jy", "5o9", "4Q4", "4DU", "1oZ", "2JX", "4m0", "4xQ", "8Jg", "22T", "Na", "0mS", "627", "77w",
|
||||
"6nn", "5Kl", "V0", "CQ", "3kM", "0NO", "41i", "7Pc", "6OC", "5jA", "0AN", "20e", "LP", "Y1", "4TL", "6ao", "78v", "bcO", "0bR", "0w3",
|
||||
"oL", "8Ef", "43X", "4b1", "4iR", "7LP", "5F", "266", "08X", "0i1", "66U", "b9E", "4JN", "7oL", "RR", "G3", "1Om", "8v", "6QA", "4db",
|
||||
"4kc", "7Na", "7w", "0PM", "H2", "2KB", "64d", "4EO", "b6D", "69T", "Pc", "07Y", "297", "dRm", "4s2", "4fS", "40w", "557", "lc", "0OQ",
|
||||
"15T", "BO", "4O2", "4ZS", "4Wc", "76i", "2Yn", "0lM", "t2", "aS", "6Ll", "4yO", "42F", "6Cm", "nR", "8Dx", "0cL", "2Vo", "6mA", "4Xb",
|
||||
"4UR", "74X", "MN", "8gd", "1Pq", "cb", "adO", "bAM", "azM", "51U", "1Ns", "9h", "SL", "04v", "4KP", "7nR", "67K", "5VA", "09F", "2Hm",
|
||||
"4X", "e1", "4hL", "7MN", "6Rn", "4gM", "j0", "3ya", "2Gl", "06G", "4Ia", "68J", "4Q0", "4DQ", "82o", "d4g", "6i", "0QS", "bPN", "a1D",
|
||||
"Ne", "0mW", "4Vy", "5S9", "4m4", "4xU", "1SZ", "22P", "my", "0NK", "41m", "7Pg", "6nj", "5Kh", "V4", "CU", "LT", "Y5", "4TH", "6ak",
|
||||
"6OG", "4zd", "0AJ", "bx", "oH", "0Lz", "4wT", "4b5", "78r", "4Yx", "0bV", "Ad", "1lu", "0i5", "66Q", "4Gz", "4iV", "7LT", "5B", "0Rx",
|
||||
"1Oi", "8r", "6QE", "4df", "4JJ", "7oH", "RV", "G7", "H6", "2KF", "6ph", "4EK", "4kg", "7Ne", "7s", "0PI", "1MX", "1X9", "4s6", "4fW",
|
||||
"5XZ", "69P", "Pg", "0sU", "06", "23F", "afr", "4yC", "4Wo", "6bL", "Os", "0lA", "0aq", "BC", "aEn", "c4E", "4ts", "5q3", "lo", "8FE",
|
||||
"347", "cn", "6NQ", "5kS", "bom", "74T", "MB", "0np", "17i", "2Vc", "6mM", "4Xn", "42J", "6Ca", "2xO", "0Ml", "4T", "0Sn", "5xa", "7MB",
|
||||
"67G", "4Fl", "09J", "2Ha", "1u2", "04z", "b5g", "aTm", "6PS", "4ep", "8WF", "9d", "6e", "8XG", "4jq", "5o1", "65v", "706", "1oR", "1z3",
|
||||
"Qq", "06K", "4Im", "68F", "6Rb", "4gA", "1LN", "2iL", "6nf", "5Kd", "V8", "CY", "mu", "0NG", "41a", "7Pk", "4m8", "4xY", "0Cw", "1F7",
|
||||
"Ni", "19r", "4Vu", "5S5", "6lW", "4Yt", "0bZ", "Ah", "oD", "0Lv", "43P", "4b9", "6OK", "4zh", "0AF", "bt", "LX", "Y9", "4TD", "6ag",
|
||||
"4JF", "7oD", "RZ", "0qh", "1Oe", "2jg", "6QI", "4dj", "4iZ", "483", "5N", "0Rt", "08P", "0i9", "5B6", "4Gv", "4Hw", "5M7", "Pk", "07Q",
|
||||
"1MT", "1X5", "472", "52r", "4kk", "7Ni", "sw", "0PE", "1nH", "2KJ", "64l", "4EG", "4Wk", "6bH", "Ow", "0lE", "02", "23B", "6Ld", "4yG",
|
||||
"4tw", "5q7", "lk", "0OY", "0au", "BG", "6ox", "5Jz", "4UZ", "74P", "MF", "0nt", "1Py", "cj", "6NU", "5kW", "42N", "6Ce", "nZ", "0Mh",
|
||||
"0cD", "2Vg", "6mI", "4Xj", "67C", "4Fh", "09N", "2He", "4P", "e9", "4hD", "7MF", "6PW", "4et", "3n9", "2ky", "SD", "0pv", "4KX", "7nZ",
|
||||
"4Q8", "4DY", "1oV", "1z7", "6a", "1Az", "4ju", "5o5", "6Rf", "4gE", "j8", "2iH", "Qu", "06O", "4Ii", "68B", "mq", "0NC", "41e", "7Po",
|
||||
"6nb", "bar", "14F", "2UL", "Nm", "19v", "4Vq", "5S1", "agl", "bBn", "0Cs", "1F3", "1I2", "0Lr", "43T", "ahm", "6lS", "4Yp", "16w", "Al",
|
||||
"2ZM", "0on", "5Da", "6ac", "6OO", "4zl", "0AB", "bp", "1Oa", "8z", "6QM", "4dn", "4JB", "aUs", "2DO", "05d", "08T", "d7D", "5B2", "4Gr",
|
||||
"bSm", "487", "5J", "0Rp", "1MP", "1X1", "476", "52v", "4Hs", "5M3", "Po", "07U", "1nL", "2KN", "64h", "4EC", "4ko", "7Nm", "ss", "0PA",
|
||||
"QJ", "06p", "4IV", "7lT", "6RY", "4gz", "1Lu", "0I5", "rV", "g7", "4jJ", "7OH", "65M", "4Df", "1oi", "2Jk", "2Ej", "04A", "4Kg", "7ne",
|
||||
"6Ph", "4eK", "h6", "2kF", "4o", "0SU", "5xZ", "7My", "4S6", "4FW", "09q", "1x9", "17R", "2VX", "4M4", "4XU", "42q", "571", "ne", "0MW",
|
||||
"v4", "cU", "6Nj", "5kh", "4Ue", "74o", "My", "0nK", "0aJ", "Bx", "6oG", "4Zd", "4tH", "6Ak", "lT", "y5", "0BV", "ad", "580", "4yx",
|
||||
"4WT", "4B5", "OH", "0lz", "4kP", "7NR", "7D", "244", "1ns", "0k3", "64W", "con", "4HL", "69g", "PP", "E1", "1Mo", "2hm", "6SC", "52I",
|
||||
"4ia", "7Lc", "5u", "0RO", "J0", "3Ya", "66f", "4GM", "b4F", "aUL", "Ra", "0qS", "8Vg", "8E", "458", "4dQ", "4o2", "4zS", "8He", "bO",
|
||||
"Lc", "0oQ", "605", "75u", "6ll", "4YO", "T2", "AS", "2yn", "0LM", "43k", "7Ra", "6MA", "4xb", "0CL", "22g", "NR", "19I", "4VN", "6cm",
|
||||
"aDO", "baM", "14y", "Cb", "mN", "8Gd", "41Z", "7PP", "axO", "53W", "1Lq", "0I1", "QN", "06t", "4IR", "68y", "65I", "4Db", "1om", "2Jo",
|
||||
"6Z", "g3", "4jN", "7OL", "6Pl", "4eO", "h2", "2kB", "2En", "04E", "4Kc", "7na", "4S2", "4FS", "09u", "d6e", "4k", "0SQ", "bRL", "a3F",
|
||||
"42u", "575", "na", "0MS", "17V", "dlo", "4M0", "4XQ", "4Ua", "74k", "3KM", "0nO", "28", "cQ", "6Nn", "5kl", "40D", "6Ao", "lP", "y1",
|
||||
"0aN", "2Tm", "6oC", "5JA", "4WP", "4B1", "OL", "18W", "0BR", "0W3", "584", "bCO", "1nw", "0k7", "64S", "4Ex", "4kT", "7NV", "sH", "0Pz",
|
||||
"1Mk", "2hi", "6SG", "4fd", "4HH", "69c", "PT", "E5", "J4", "2ID", "66b", "4GI", "4ie", "7Lg", "5q", "0RK", "1OZ", "8A", "4q4", "4dU",
|
||||
"4Jy", "5O9", "Re", "0qW", "Lg", "0oU", "5DZ", "6aX", "4o6", "4zW", "0Ay", "bK", "2yj", "0LI", "43o", "6BD", "6lh", "4YK", "T6", "AW",
|
||||
"NV", "0md", "4VJ", "6ci", "6ME", "4xf", "0CH", "22c", "mJ", "0Nx", "4uV", "7PT", "6nY", "baI", "1pu", "Cf", "6V", "0Ql", "4jB", "aus",
|
||||
"65E", "4Dn", "1oa", "2Jc", "QB", "06x", "b7e", "68u", "5b2", "4gr", "8UD", "dSL", "4g", "8ZE", "4hs", "5m3", "67t", "724", "09y", "1x1",
|
||||
"Ss", "04I", "4Ko", "7nm", "azr", "4eC", "1NL", "9W", "24", "21D", "6Nb", "bAr", "4Um", "74g", "Mq", "0nC", "0cs", "1f3", "79W", "bbn",
|
||||
"42y", "579", "nm", "394", "365", "al", "588", "4yp", "bmo", "76V", "1i2", "0lr", "0aB", "Bp", "6oO", "4Zl", "40H", "6Ac", "2zM", "0On",
|
||||
"4HD", "69o", "PX", "E9", "1Mg", "2he", "6SK", "4fh", "4kX", "7NZ", "7L", "0Pv", "3N9", "2Ky", "6pW", "4Et", "4Ju", "5O5", "Ri", "05S",
|
||||
"1OV", "8M", "450", "4dY", "4ii", "7Lk", "qu", "0RG", "J8", "2IH", "66n", "4GE", "6ld", "4YG", "0bi", "2WJ", "ow", "0LE", "43c", "6BH",
|
||||
"6Ox", "5jz", "0Au", "bG", "Lk", "0oY", "4Tw", "5Q7", "6nU", "5KW", "14q", "Cj", "mF", "0Nt", "41R", "7PX", "6MI", "4xj", "0CD", "22o",
|
||||
"NZ", "0mh", "4VF", "6ce", "65A", "4Dj", "1oe", "2Jg", "6R", "0Qh", "4jF", "7OD", "5b6", "4gv", "1Ly", "0I9", "QF", "0rt", "4IZ", "68q",
|
||||
"67p", "5Vz", "1mT", "1x5", "4c", "0SY", "4hw", "5m7", "6Pd", "4eG", "1NH", "9S", "Sw", "04M", "4Kk", "7ni", "4Ui", "74c", "Mu", "0nG",
|
||||
"20", "cY", "6Nf", "5kd", "4vu", "5s5", "ni", "390", "0cw", "1f7", "4M8", "4XY", "4WX", "4B9", "OD", "0lv", "0BZ", "ah", "6LW", "4yt",
|
||||
"40L", "6Ag", "lX", "y9", "0aF", "Bt", "6oK", "4Zh", "1Mc", "2ha", "6SO", "4fl", "5Xa", "69k", "2FM", "07f", "83N", "d5F", "6pS", "4Ep",
|
||||
"bQo", "a0e", "7H", "0Pr", "1OR", "8I", "454", "50t", "4Jq", "5O1", "Rm", "05W", "08g", "2IL", "66j", "4GA", "4im", "7Lo", "5y", "0RC",
|
||||
"os", "0LA", "43g", "6BL", "78I", "4YC", "0bm", "2WN", "Lo", "8fE", "4Ts", "5Q3", "aen", "cPM", "0Aq", "bC", "mB", "0Np", "41V", "ajo",
|
||||
"6nQ", "5KS", "14u", "Cn", "2XO", "0ml", "4VB", "6ca", "6MM", "4xn", "1Sa", "22k", "Sj", "04P", "4Kv", "5N6", "443", "4eZ", "1NU", "9N",
|
||||
"pv", "0SD", "4hj", "7Mh", "67m", "4FF", "1mI", "2HK", "2GJ", "2", "4IG", "68l", "6RH", "4gk", "1Ld", "2if", "6O", "0Qu", "5zz", "7OY",
|
||||
"5A7", "4Dw", "1ox", "0j8", "15r", "Bi", "6oV", "4Zu", "40Q", "4a8", "lE", "0Ow", "0BG", "au", "6LJ", "4yi", "4WE", "6bf", "OY", "Z8",
|
||||
"U9", "2VI", "6mg", "4XD", "4vh", "6CK", "nt", "0MF", "1PW", "cD", "4n9", "5ky", "4Ut", "5P4", "Mh", "0nZ", "4ip", "5l0", "5d", "9Kg",
|
||||
"08z", "1y2", "66w", "737", "4Jl", "7on", "Rp", "05J", "1OO", "8T", "6Qc", "50i", "4kA", "7NC", "7U", "0Po", "1nb", "dqS", "64F", "4Em",
|
||||
"b6f", "69v", "PA", "0ss", "8TG", "dRO", "5c1", "4fq", "6MP", "4xs", "376", "22v", "NC", "0mq", "bll", "77U", "6nL", "5KN", "14h", "Cs",
|
||||
"3ko", "0Nm", "41K", "7PA", "6Oa", "4zB", "37", "20G", "Lr", "8fX", "4Tn", "6aM", "78T", "bcm", "0bp", "AB", "on", "387", "43z", "5r2",
|
||||
"447", "51w", "1NQ", "9J", "Sn", "04T", "4Kr", "5N2", "67i", "4FB", "09d", "2HO", "4z", "1Ca", "4hn", "7Ml", "6RL", "4go", "8UY", "2ib",
|
||||
"2GN", "6", "4IC", "68h", "5A3", "4Ds", "82M", "d4E", "6K", "0Qq", "bPl", "a1f", "40U", "akl", "lA", "0Os", "15v", "Bm", "6oR", "4Zq",
|
||||
"4WA", "6bb", "2YL", "0lo", "0BC", "aq", "6LN", "4ym", "42d", "6CO", "np", "0MB", "0cn", "2VM", "6mc", "5Ha", "4Up", "5P0", "Ml", "8gF",
|
||||
"1PS", "1E2", "adm", "bAo", "1lW", "1y6", "4R9", "4GX", "4it", "5l4", "qh", "0RZ", "i9", "8P", "6Qg", "4dD", "4Jh", "7oj", "Rt", "05N",
|
||||
"1nf", "2Kd", "64B", "4Ei", "4kE", "7NG", "7Q", "f8", "1Mz", "2hx", "5c5", "4fu", "4HY", "69r", "PE", "0sw", "NG", "0mu", "5Fz", "6cx",
|
||||
"6MT", "4xw", "0CY", "0V8", "3kk", "0Ni", "41O", "7PE", "6nH", "5KJ", "14l", "Cw", "Lv", "0oD", "4Tj", "6aI", "6Oe", "4zF", "33", "bZ",
|
||||
"oj", "0LX", "4wv", "5r6", "6ly", "4YZ", "0bt", "AF", "4v", "0SL", "4hb", "awS", "67e", "4FN", "K3", "2HC", "Sb", "04X", "b5E", "aTO",
|
||||
"4p3", "4eR", "8Wd", "9F", "6G", "257", "4jS", "7OQ", "65T", "cnm", "1op", "0j0", "QS", "D2", "4IO", "68d", "7Ba", "4gc", "1Ll", "2in",
|
||||
"0BO", "23d", "6LB", "4ya", "4WM", "6bn", "OQ", "Z0", "0aS", "Ba", "aEL", "c4g", "40Y", "4a0", "lM", "8Fg", "8If", "cL", "4n1", "5kq",
|
||||
"616", "74v", "3KP", "0nR", "U1", "2VA", "6mo", "4XL", "42h", "6CC", "2xm", "0MN", "4Jd", "7of", "Rx", "05B", "i5", "2jE", "6Qk", "4dH",
|
||||
"4ix", "5l8", "5l", "0RV", "08r", "2IY", "4R5", "4GT", "4HU", "7mW", "PI", "07s", "1Mv", "0H6", "5c9", "4fy", "4kI", "7NK", "sU", "f4",
|
||||
"1nj", "2Kh", "64N", "4Ee", "6nD", "5KF", "1ph", "2Uj", "mW", "x6", "41C", "7PI", "593", "5hZ", "0CU", "0V4", "NK", "0my", "4VW", "4C6",
|
||||
"4L7", "4YV", "0bx", "AJ", "of", "0LT", "43r", "562", "6Oi", "4zJ", "w7", "bV", "Lz", "0oH", "4Tf", "6aE", "67a", "4FJ", "K7", "2HG",
|
||||
"4r", "0SH", "4hf", "7Md", "4p7", "4eV", "1NY", "9B", "Sf", "0pT", "4Kz", "7nx", "65P", "5TZ", "1ot", "0j4", "6C", "0Qy", "4jW", "7OU",
|
||||
"6RD", "4gg", "1Lh", "2ij", "QW", "D6", "4IK", "7lI", "4WI", "6bj", "OU", "Z4", "0BK", "ay", "6LF", "4ye", "4tU", "4a4", "lI", "2o9",
|
||||
"0aW", "Be", "6oZ", "4Zy", "4Ux", "5P8", "Md", "0nV", "8Ib", "cH", "4n5", "5ku", "42l", "6CG", "nx", "0MJ", "U5", "2VE", "6mk", "4XH",
|
||||
"i1", "8X", "6Qo", "4dL", "5ZA", "7ob", "2Dm", "05F", "08v", "d7f", "4R1", "4GP", "bSO", "a2E", "5h", "0RR", "1Mr", "0H2", "ayL", "52T",
|
||||
"4HQ", "69z", "PM", "07w", "1nn", "2Kl", "64J", "4Ea", "4kM", "7NO", "7Y", "f0", "mS", "x2", "41G", "7PM", "aDR", "5KB", "14d", "2Un",
|
||||
"NO", "19T", "4VS", "4C2", "597", "bBL", "0CQ", "0V0", "ob", "0LP", "43v", "566", "4L3", "4YR", "16U", "AN", "2Zo", "0oL", "4Tb", "6aA",
|
||||
"6Om", "4zN", "w3", "bR", "4oT", "4z5", "wH", "0Tz", "0zV", "Yd", "5D8", "4Ax", "4LH", "6yk", "TT", "A5", "0YJ", "zx", "6WG", "4bd",
|
||||
"4me", "6XF", "1q", "0VK", "N4", "2MD", "62b", "4CI", "4Ny", "5K9", "Ve", "0uW", "1KZ", "xI", "4u4", "5pt", "4k6", "5nv", "0Ey", "fK",
|
||||
"Hg", "0kU", "641", "6eX", "6hh", "5Mj", "P6", "EW", "29b", "0HI", "47o", "6FD", "6IE", "48n", "0GH", "dz", "JV", "0id", "4RJ", "6gi",
|
||||
"6jY", "beI", "0dT", "Gf", "iJ", "0Jx", "4qV", "4d7", "UN", "02t", "4MR", "4X3", "a8G", "57W", "0XP", "0M1", "2Z", "c3", "4nN", "7KL",
|
||||
"61I", "5PC", "1km", "2No", "2An", "00E", "4Oc", "7ja", "6Tl", "4aO", "l2", "yS", "0k", "0WQ", "58V", "a7F", "4W2", "4BS", "84m", "ZO",
|
||||
"13V", "E", "4I0", "5Lp", "46u", "535", "ja", "0IS", "68", "gQ", "6Jn", "5ol", "4Qa", "6dB", "3OM", "0jO", "0eN", "2Pm", "6kC", "5NA",
|
||||
"44D", "6Eo", "hP", "99", "0FR", "0S3", "abM", "49t", "4SP", "4F1", "KL", "8af", "0zR", "0o3", "60W", "ckn", "4oP", "4z1", "3D", "204",
|
||||
"0YN", "2lm", "6WC", "56I", "4LL", "6yo", "TP", "A1", "N0", "903", "62f", "4CM", "4ma", "6XB", "1u", "0VO", "8Rg", "xM", "418", "54x",
|
||||
"b0F", "aQL", "Va", "0uS", "Hc", "0kQ", "645", "71u", "4k2", "5nr", "8Le", "fO", "29f", "0HM", "47k", "7Va", "6hl", "5Mn", "P2", "ES",
|
||||
"JR", "1yA", "4RN", "6gm", "6IA", "48j", "0GL", "26g", "iN", "8Cd", "45Z", "4d3", "hYv", "beM", "0dP", "Gb", "6VY", "4cz", "0XT", "0M5",
|
||||
"UJ", "02p", "4MV", "4X7", "61M", "5PG", "1ki", "Xz", "vV", "c7", "4nJ", "7KH", "6Th", "4aK", "l6", "yW", "2Aj", "00A", "4Og", "6zD",
|
||||
"4W6", "4BW", "0yy", "ZK", "0o", "0WU", "58R", "6YX", "46q", "531", "je", "0IW", "13R", "A", "4I4", "5Lt", "4Qe", "6dF", "Iy", "0jK",
|
||||
"r4", "gU", "6Jj", "5oh", "4pH", "6Ek", "hT", "0Kf", "0eJ", "Fx", "6kG", "5NE", "4ST", "4F5", "KH", "0hz", "0FV", "ed", "5x8", "49p",
|
||||
"bvs", "6yc", "2BM", "03f", "0YB", "zp", "6WO", "4bl", "bUo", "a4e", "3H", "0Tr", "87N", "Yl", "5D0", "4Ap", "4Nq", "5K1", "Vm", "01W",
|
||||
"1KR", "xA", "414", "54t", "4mm", "6XN", "1y", "0VC", "0xo", "2ML", "62j", "4CA", "7xA", "5Mb", "0fm", "2SN", "ks", "0HA", "47g", "6FL",
|
||||
"aan", "bDl", "0Eq", "fC", "Ho", "8bE", "4Ps", "5U3", "5Z2", "5OS", "10u", "Gn", "iB", "0Jp", "45V", "ano", "6IM", "48f", "1Wa", "dr",
|
||||
"3Ln", "0il", "4RB", "6ga", "2R", "0Uh", "4nF", "7KD", "61A", "5PK", "1ke", "Xv", "UF", "0vt", "4MZ", "6xy", "5f6", "4cv", "0XX", "0M9",
|
||||
"0c", "0WY", "4lw", "5i7", "63p", "5Rz", "0yu", "ZG", "Ww", "00M", "4Ok", "6zH", "6Td", "4aG", "0Zi", "2oJ", "60", "gY", "6Jf", "5od",
|
||||
"4Qi", "6dJ", "Iu", "0jG", "0gw", "M", "4I8", "5Lx", "4ru", "5w5", "ji", "1Yz", "0FZ", "eh", "5x4", "5mU", "4SX", "4F9", "KD", "0hv",
|
||||
"0eF", "Ft", "6kK", "5NI", "44L", "6Eg", "hX", "91", "0YF", "zt", "6WK", "4bh", "4LD", "6yg", "TX", "A9", "0zZ", "Yh", "5D4", "4At",
|
||||
"4oX", "4z9", "3L", "0Tv", "1KV", "xE", "410", "54p", "4Nu", "5K5", "Vi", "01S", "N8", "2MH", "62n", "4CE", "4mi", "6XJ", "uu", "0VG",
|
||||
"kw", "0HE", "47c", "6FH", "6hd", "5Mf", "0fi", "2SJ", "Hk", "0kY", "4Pw", "5U7", "6Kx", "5nz", "0Eu", "fG", "iF", "0Jt", "45R", "6Dy",
|
||||
"5Z6", "5OW", "0dX", "Gj", "JZ", "0ih", "4RF", "6ge", "6II", "48b", "0GD", "dv", "61E", "5PO", "1ka", "Xr", "2V", "0Ul", "4nB", "aqs",
|
||||
"5f2", "4cr", "8QD", "39V", "UB", "02x", "795", "aRo", "63t", "764", "0yq", "ZC", "0g", "9Nd", "4ls", "5i3", "7DA", "4aC", "0Zm", "2oN",
|
||||
"Ws", "00I", "4Oo", "6zL", "4Qm", "6dN", "Iq", "0jC", "64", "25D", "6Jb", "bEr", "46y", "539", "jm", "9Pf", "0gs", "I", "aCl", "bfn",
|
||||
"bio", "72V", "1m2", "0hr", "325", "el", "5x0", "49x", "44H", "6Ec", "3nl", "95", "0eB", "Fp", "6kO", "5NM", "4mt", "5h4", "uh", "0VZ",
|
||||
"0xv", "2MU", "4V9", "4CX", "4Nh", "7kj", "Vt", "01N", "m9", "xX", "6Ug", "54m", "4oE", "6Zf", "3Q", "b8", "0zG", "Yu", "60B", "4Ai",
|
||||
"4LY", "4Y8", "TE", "0ww", "1Iz", "zi", "5g5", "4bu", "5y7", "5lV", "0GY", "dk", "JG", "0iu", "5Bz", "6gx", "6jH", "5OJ", "0dE", "Gw",
|
||||
"3ok", "82", "45O", "6Dd", "6Ke", "5ng", "73", "fZ", "Hv", "0kD", "4Pj", "6eI", "6hy", "7m9", "0ft", "EF", "kj", "0HX", "4sv", "5v6",
|
||||
"Wn", "00T", "4Or", "5J2", "407", "55w", "0Zp", "yB", "0z", "1Ga", "4ln", "6YM", "63i", "4BB", "0yl", "2LO", "2CN", "02e", "4MC", "7hA",
|
||||
"6VL", "4co", "0XA", "2mb", "2K", "0Uq", "bTl", "a5f", "5E3", "5PR", "86M", "Xo", "11v", "Fm", "6kR", "5NP", "44U", "aol", "hA", "0Ks",
|
||||
"0FC", "eq", "6HN", "49e", "4SA", "6fb", "3Mm", "0ho", "0gn", "T", "6ic", "5La", "46d", "6GO", "jp", "0IB", "0Dr", "1A2", "hyT", "bEo",
|
||||
"4Qp", "5T0", "Il", "8cF", "0xr", "2MQ", "62w", "777", "4mp", "5h0", "1d", "9Og", "1KO", "2nM", "6Uc", "54i", "4Nl", "7kn", "Vp", "01J",
|
||||
"0zC", "Yq", "60F", "4Am", "4oA", "6Zb", "3U", "0To", "8PG", "zm", "5g1", "4bq", "786", "aSl", "TA", "0ws", "JC", "0iq", "bhl", "73U",
|
||||
"5y3", "5lR", "336", "do", "3oo", "86", "45K", "7TA", "6jL", "5ON", "0dA", "Gs", "Hr", "8bX", "4Pn", "6eM", "6Ka", "5nc", "77", "24G",
|
||||
"kn", "8AD", "47z", "5v2", "aBo", "bgm", "0fp", "EB", "403", "4aZ", "0Zt", "yF", "Wj", "00P", "4Ov", "5J6", "63m", "4BF", "0yh", "ZZ",
|
||||
"tv", "0WD", "4lj", "6YI", "6VH", "4ck", "0XE", "2mf", "2CJ", "02a", "4MG", "6xd", "5E7", "5PV", "1kx", "Xk", "2O", "0Uu", "bTh", "7KY",
|
||||
"44Q", "4e8", "hE", "0Kw", "11r", "Fi", "6kV", "5NT", "4SE", "6ff", "KY", "0hk", "0FG", "eu", "6HJ", "49a", "4rh", "6GK", "jt", "0IF",
|
||||
"Q9", "P", "6ig", "5Le", "4Qt", "5T4", "Ih", "0jZ", "0Dv", "gD", "4j9", "5oy", "aD0", "7kb", "3PL", "01F", "m1", "xP", "6Uo", "54e",
|
||||
"59U", "a6E", "1h", "0VR", "85n", "196", "4V1", "4CP", "4LQ", "4Y0", "TM", "03w", "0YS", "za", "a9D", "56T", "4oM", "6Zn", "3Y", "b0",
|
||||
"0zO", "2Ol", "60J", "4Aa", "7za", "5OB", "0dM", "2Qn", "iS", "0Ja", "45G", "6Dl", "acN", "48w", "0GQ", "dc", "JO", "94L", "4RS", "4G2",
|
||||
"4H3", "5Ms", "12U", "EN", "kb", "0HP", "47v", "526", "6Km", "5no", "s3", "fR", "3NN", "0kL", "4Pb", "6eA", "0r", "0WH", "4lf", "6YE",
|
||||
"63a", "4BJ", "O7", "ZV", "Wf", "0tT", "4Oz", "6zY", "4t7", "4aV", "0Zx", "yJ", "2C", "0Uy", "4nW", "7KU", "61P", "5PZ", "1kt", "Xg",
|
||||
"UW", "02m", "4MK", "6xh", "6VD", "4cg", "0XI", "2mj", "0FK", "ey", "6HF", "49m", "4SI", "6fj", "KU", "0hg", "0eW", "Fe", "6kZ", "5NX",
|
||||
"4pU", "4e4", "hI", "2k9", "0Dz", "gH", "4j5", "5ou", "4Qx", "5T8", "Id", "0jV", "Q5", "DT", "6ik", "5Li", "46l", "6GG", "jx", "0IJ",
|
||||
"m5", "xT", "6Uk", "54a", "4Nd", "7kf", "Vx", "01B", "0xz", "192", "4V5", "4CT", "4mx", "5h8", "1l", "0VV", "0YW", "ze", "5g9", "4by",
|
||||
"4LU", "4Y4", "TI", "03s", "0zK", "Yy", "60N", "4Ae", "4oI", "6Zj", "wU", "b4", "iW", "0Je", "45C", "6Dh", "6jD", "5OF", "0dI", "2Qj",
|
||||
"JK", "0iy", "4RW", "4G6", "6IX", "48s", "0GU", "dg", "kf", "0HT", "47r", "522", "4H7", "5Mw", "0fx", "EJ", "Hz", "0kH", "4Pf", "6eE",
|
||||
"6Ki", "5nk", "s7", "fV", "63e", "4BN", "O3", "ZR", "0v", "0WL", "4lb", "6YA", "4t3", "4aR", "8Sd", "yN", "Wb", "00X", "b1E", "aPO",
|
||||
"61T", "bzL", "1kp", "Xc", "2G", "217", "4nS", "7KQ", "7Fa", "4cc", "0XM", "2mn", "US", "02i", "4MO", "6xl", "4SM", "6fn", "KQ", "0hc",
|
||||
"0FO", "27d", "6HB", "49i", "44Y", "4e0", "hM", "8Bg", "0eS", "Fa", "aAL", "bdN", "656", "70v", "3OP", "0jR", "8Mf", "gL", "4j1", "5oq",
|
||||
"46h", "6GC", "28e", "0IN", "Q1", "X", "6io", "5Lm", "6KV", "5nT", "1Uz", "fi", "HE", "0kw", "4PY", "4E8", "6hJ", "5MH", "0fG", "Eu",
|
||||
"kY", "0Hk", "47M", "6Ff", "6Ig", "48L", "51", "dX", "Jt", "0iF", "4Rh", "6gK", "4J9", "5Oy", "0dv", "GD", "ih", "0JZ", "4qt", "5t4",
|
||||
"4ov", "5j6", "3b", "0TX", "0zt", "YF", "60q", "4AZ", "4Lj", "6yI", "Tv", "03L", "0Yh", "zZ", "6We", "4bF", "4mG", "6Xd", "1S", "0Vi",
|
||||
"0xE", "2Mf", "6vH", "4Ck", "bth", "7kY", "VG", "0uu", "1Kx", "xk", "5e7", "5pV", "13t", "g", "5Y3", "5LR", "46W", "amn", "jC", "0Iq",
|
||||
"0DA", "gs", "6JL", "5oN", "4QC", "70I", "3Oo", "0jm", "0el", "2PO", "6ka", "5Nc", "44f", "6EM", "hr", "8BX", "0Fp", "eB", "abo", "49V",
|
||||
"4Sr", "5V2", "Kn", "8aD", "Ul", "02V", "4Mp", "5H0", "425", "57u", "0Xr", "2mQ", "2x", "0UB", "4nl", "7Kn", "61k", "5Pa", "1kO", "2NM",
|
||||
"2AL", "00g", "4OA", "6zb", "6TN", "4am", "0ZC", "yq", "0I", "0Ws", "58t", "a7d", "5G1", "4Bq", "84O", "Zm", "HA", "0ks", "bjn", "71W",
|
||||
"6KR", "5nP", "314", "fm", "29D", "0Ho", "47I", "6Fb", "6hN", "5ML", "0fC", "Eq", "Jp", "0iB", "4Rl", "6gO", "6Ic", "48H", "55", "26E",
|
||||
"il", "8CF", "45x", "508", "hYT", "beo", "0dr", "1a2", "0zp", "YB", "60u", "755", "4or", "5j2", "3f", "9Me", "0Yl", "2lO", "6Wa", "4bB",
|
||||
"4Ln", "6yM", "Tr", "03H", "0xA", "2Mb", "62D", "4Co", "4mC", "7HA", "1W", "0Vm", "8RE", "xo", "5e3", "54Z", "b0d", "aQn", "VC", "01y",
|
||||
"46S", "6Gx", "jG", "0Iu", "0gY", "c", "5Y7", "5LV", "4QG", "6dd", "3Ok", "0ji", "0DE", "gw", "6JH", "5oJ", "44b", "6EI", "hv", "0KD",
|
||||
"0eh", "FZ", "6ke", "5Ng", "4Sv", "5V6", "Kj", "0hX", "0Ft", "eF", "6Hy", "49R", "421", "4cX", "0Xv", "2mU", "Uh", "02R", "4Mt", "5H4",
|
||||
"61o", "5Pe", "M9", "XX", "vt", "0UF", "4nh", "7Kj", "6TJ", "4ai", "0ZG", "yu", "WY", "B8", "4OE", "6zf", "5G5", "4Bu", "1iz", "Zi",
|
||||
"0M", "0Ww", "4lY", "4y8", "6hB", "aW1", "0fO", "2Sl", "kQ", "0Hc", "47E", "6Fn", "aaL", "bDN", "0ES", "fa", "HM", "8bg", "4PQ", "4E0",
|
||||
"4J1", "5Oq", "10W", "GL", "3oP", "0JR", "45t", "504", "6Io", "48D", "59", "dP", "3LL", "0iN", "5BA", "6gC", "4Lb", "6yA", "2Bo", "03D",
|
||||
"o3", "zR", "6Wm", "4bN", "bUM", "a4G", "3j", "0TP", "87l", "YN", "4T3", "4AR", "4NS", "7kQ", "VO", "01u", "1Kp", "xc", "hfw", "54V",
|
||||
"4mO", "6Xl", "uS", "0Va", "0xM", "2Mn", "62H", "4Cc", "0DI", "25b", "6JD", "5oF", "4QK", "6dh", "IW", "0je", "0gU", "o", "6iX", "5LZ",
|
||||
"4rW", "4g6", "jK", "0Iy", "0Fx", "eJ", "4h7", "5mw", "4Sz", "6fY", "Kf", "0hT", "S7", "FV", "6ki", "5Nk", "44n", "6EE", "hz", "0KH",
|
||||
"2p", "0UJ", "4nd", "7Kf", "61c", "5Pi", "M5", "XT", "Ud", "0vV", "4Mx", "5H8", "4v5", "4cT", "0Xz", "2mY", "0A", "1GZ", "4lU", "4y4",
|
||||
"5G9", "4By", "0yW", "Ze", "WU", "B4", "4OI", "6zj", "6TF", "4ae", "0ZK", "yy", "kU", "0Hg", "47A", "6Fj", "6hF", "5MD", "0fK", "Ey",
|
||||
"HI", "2K9", "4PU", "4E4", "6KZ", "5nX", "0EW", "fe", "id", "0JV", "45p", "500", "4J5", "5Ou", "0dz", "GH", "Jx", "0iJ", "4Rd", "6gG",
|
||||
"6Ik", "5li", "q5", "dT", "o7", "zV", "6Wi", "4bJ", "4Lf", "6yE", "Tz", "0wH", "0zx", "YJ", "4T7", "4AV", "4oz", "6ZY", "3n", "0TT",
|
||||
"1Kt", "xg", "6UX", "54R", "4NW", "7kU", "VK", "01q", "0xI", "2Mj", "62L", "4Cg", "4mK", "6Xh", "uW", "0Ve", "4QO", "6dl", "IS", "0ja",
|
||||
"0DM", "25f", "7Za", "5oB", "4rS", "4g2", "jO", "9PD", "0gQ", "k", "aCN", "685", "674", "72t", "Kb", "0hP", "8Od", "eN", "4h3", "49Z",
|
||||
"44j", "6EA", "3nN", "0KL", "S3", "FR", "6km", "5No", "61g", "5Pm", "M1", "XP", "2t", "0UN", "ad0", "7Kb", "429", "4cP", "8Qf", "39t",
|
||||
"0c3", "02Z", "b3G", "aRM", "63V", "bxN", "0yS", "Za", "0E", "235", "4lQ", "4y0", "6TB", "4aa", "0ZO", "2ol", "WQ", "B0", "4OM", "6zn",
|
||||
"4i4", "5lt", "1WZ", "dI", "Je", "0iW", "4Ry", "5W9", "6jj", "5Oh", "R4", "GU", "iy", "0JK", "45m", "6DF", "6KG", "5nE", "0EJ", "fx",
|
||||
"HT", "0kf", "4PH", "6ek", "5X8", "5MY", "0fV", "Ed", "kH", "0Hz", "4sT", "4f5", "4mV", "4x7", "1B", "0Vx", "0xT", "0m5", "62Q", "4Cz",
|
||||
"4NJ", "7kH", "VV", "C7", "1Ki", "xz", "6UE", "54O", "4og", "6ZD", "3s", "0TI", "L6", "YW", "6th", "4AK", "6l9", "6yX", "Tg", "0wU",
|
||||
"0Yy", "zK", "4w6", "4bW", "11T", "FO", "4K2", "5Nr", "44w", "517", "hc", "0KQ", "p2", "eS", "6Hl", "49G", "4Sc", "72i", "3MO", "0hM",
|
||||
"0gL", "v", "6iA", "5LC", "46F", "6Gm", "jR", "1YA", "0DP", "gb", "hyv", "bEM", "4QR", "4D3", "IN", "8cd", "WL", "00v", "4OP", "4Z1",
|
||||
"hgt", "55U", "0ZR", "0O3", "0X", "a1", "4lL", "6Yo", "63K", "5RA", "0yN", "2Lm", "2Cl", "02G", "4Ma", "6xB", "6Vn", "4cM", "n0", "39i",
|
||||
"2i", "0US", "bTN", "a5D", "4U0", "5Pp", "86o", "XM", "Ja", "0iS", "667", "73w", "4i0", "48Y", "8Ng", "dM", "3oM", "0JO", "45i", "6DB",
|
||||
"6jn", "5Ol", "R0", "GQ", "HP", "0kb", "4PL", "6eo", "6KC", "5nA", "0EN", "24e", "kL", "8Af", "47X", "4f1", "aBM", "696", "0fR", "0s3",
|
||||
"0xP", "0m1", "62U", "byM", "4mR", "4x3", "1F", "226", "1Km", "2no", "6UA", "54K", "4NN", "7kL", "VR", "C3", "L2", "YS", "60d", "4AO",
|
||||
"4oc", "7Ja", "3w", "0TM", "8Pe", "zO", "4w2", "4bS", "b2D", "aSN", "Tc", "03Y", "44s", "513", "hg", "0KU", "0ey", "FK", "4K6", "5Nv",
|
||||
"4Sg", "6fD", "3MK", "0hI", "p6", "eW", "6Hh", "49C", "46B", "6Gi", "jV", "0Id", "0gH", "r", "6iE", "5LG", "4QV", "4D7", "IJ", "0jx",
|
||||
"0DT", "gf", "6JY", "bEI", "5d8", "4ax", "0ZV", "yd", "WH", "00r", "4OT", "4Z5", "63O", "4Bd", "0yJ", "Zx", "tT", "a5", "4lH", "6Yk",
|
||||
"6Vj", "4cI", "n4", "2mD", "Uy", "02C", "4Me", "6xF", "4U4", "5Pt", "1kZ", "XI", "2m", "0UW", "4ny", "5k9", "6jb", "ber", "0do", "2QL",
|
||||
"iq", "0JC", "45e", "6DN", "acl", "48U", "0Gs", "dA", "Jm", "94n", "4Rq", "5W1", "5X0", "5MQ", "12w", "El", "1M2", "0Hr", "47T", "alm",
|
||||
"6KO", "5nM", "0EB", "fp", "3Nl", "0kn", "bjs", "6ec", "4NB", "aQs", "3Pn", "01d", "1Ka", "xr", "6UM", "54G", "59w", "a6g", "1J", "0Vp",
|
||||
"85L", "d3D", "5F2", "4Cr", "4Ls", "5I3", "To", "03U", "0Yq", "zC", "436", "56v", "4oo", "6ZL", "ws", "0TA", "0zm", "2ON", "60h", "4AC",
|
||||
"42", "27B", "6Hd", "49O", "4Sk", "6fH", "Kw", "0hE", "0eu", "FG", "6kx", "5Nz", "4pw", "5u7", "hk", "0KY", "0DX", "gj", "5z6", "5oW",
|
||||
"4QZ", "6dy", "IF", "0jt", "0gD", "Dv", "6iI", "5LK", "46N", "6Ge", "jZ", "0Ih", "0P", "a9", "4lD", "6Yg", "63C", "4Bh", "0yF", "Zt",
|
||||
"WD", "0tv", "4OX", "4Z9", "5d4", "4at", "0ZZ", "yh", "2a", "1Ez", "4nu", "5k5", "4U8", "5Px", "1kV", "XE", "Uu", "02O", "4Mi", "6xJ",
|
||||
"6Vf", "4cE", "n8", "2mH", "iu", "0JG", "45a", "6DJ", "6jf", "5Od", "R8", "GY", "Ji", "1yz", "4Ru", "5W5", "4i8", "48Q", "0Gw", "dE",
|
||||
"kD", "0Hv", "47P", "4f9", "5X4", "5MU", "0fZ", "Eh", "HX", "0kj", "4PD", "6eg", "6KK", "5nI", "0EF", "ft", "1Ke", "xv", "6UI", "54C",
|
||||
"4NF", "7kD", "VZ", "0uh", "0xX", "0m9", "5F6", "4Cv", "4mZ", "6Xy", "1N", "0Vt", "0Yu", "zG", "432", "56r", "4Lw", "5I7", "Tk", "03Q",
|
||||
"0zi", "2OJ", "60l", "4AG", "4ok", "6ZH", "ww", "0TE", "4So", "6fL", "Ks", "0hA", "46", "27F", "7XA", "49K", "4ps", "5u3", "ho", "8BE",
|
||||
"0eq", "FC", "aAn", "bdl", "bkm", "70T", "IB", "0jp", "307", "gn", "5z2", "5oS", "46J", "6Ga", "28G", "0Il", "13i", "z", "6iM", "5LO",
|
||||
"63G", "4Bl", "0yB", "Zp", "0T", "0Wn", "58i", "6Yc", "5d0", "4ap", "8SF", "yl", "1q2", "00z", "b1g", "aPm", "61v", "746", "1kR", "XA",
|
||||
"2e", "9Lf", "4nq", "5k1", "6Vb", "4cA", "0Xo", "2mL", "Uq", "02K", "4Mm", "6xN", "8YG", "7e", "5n1", "4kq", "716", "64v", "2KP", "1nR",
|
||||
"07K", "Pq", "69F", "4Hm", "4fA", "6Sb", "2hL", "1MN", "0Rn", "5T", "7LB", "5ya", "4Gl", "66G", "2Ia", "08J", "05z", "1t2", "aUm", "b4g",
|
||||
"4dp", "5a0", "8d", "8VF", "bn", "357", "4zr", "6OQ", "75T", "bnm", "0op", "LB", "Ar", "16i", "4Yn", "6lM", "6Ba", "43J", "0Ll", "2yO",
|
||||
"22F", "16", "4xC", "agr", "6cL", "4Vo", "0mA", "Ns", "CC", "14X", "bal", "aDn", "5p3", "4us", "8GE", "mo", "5L7", "4Iw", "06Q", "Qk",
|
||||
"1Y5", "1LT", "53r", "462", "7Oi", "4jk", "0QE", "rw", "2JJ", "1oH", "4DG", "65l", "7nD", "4KF", "0ph", "SZ", "2kg", "1Ne", "4ej", "6PI",
|
||||
"493", "4hZ", "0St", "4N", "0h9", "09P", "4Fv", "5C6", "4Xt", "6mW", "023", "0cZ", "0Mv", "nD", "4c9", "42P", "5kI", "6NK", "ct", "1Pg",
|
||||
"X9", "MX", "74N", "4UD", "4ZE", "6of", "BY", "W8", "0OG", "lu", "6AJ", "40a", "4yY", "4l8", "aE", "0Bw", "18r", "Oi", "5R5", "4Wu",
|
||||
"4EY", "4P8", "2KT", "1nV", "8YC", "7a", "5n5", "4ku", "4fE", "6Sf", "2hH", "k8", "07O", "Pu", "69B", "4Hi", "4Gh", "66C", "2Ie", "08N",
|
||||
"d9", "5P", "7LF", "4iD", "4dt", "5a4", "2jy", "3o9", "0qv", "RD", "7oZ", "4JX", "6ay", "4TZ", "0ot", "LF", "bj", "0AX", "4zv", "6OU",
|
||||
"6Be", "43N", "0Lh", "oZ", "Av", "0bD", "4Yj", "6lI", "6cH", "4Vk", "0mE", "Nw", "22B", "12", "4xG", "6Md", "5p7", "4uw", "0NY", "mk",
|
||||
"CG", "1pT", "5Kz", "6nx", "1Y1", "1LP", "53v", "466", "5L3", "4Is", "06U", "Qo", "2JN", "1oL", "4DC", "65h", "7Om", "4jo", "0QA", "rs",
|
||||
"9z", "1Na", "4en", "6PM", "aTs", "4KB", "04d", "2EO", "d6D", "09T", "4Fr", "5C2", "497", "bRm", "0Sp", "4J", "0Mr", "1H2", "aim", "42T",
|
||||
"4Xp", "6mS", "027", "17w", "0nn", "3Kl", "74J", "5Ea", "5kM", "6NO", "cp", "1Pc", "0OC", "lq", "6AN", "40e", "4ZA", "6ob", "2TL", "0ao",
|
||||
"18v", "Om", "5R1", "4Wq", "bCn", "afl", "aA", "0Bs", "07C", "Py", "69N", "4He", "4fI", "6Sj", "2hD", "k4", "0PW", "7m", "5n9", "4ky",
|
||||
"4EU", "4P4", "2KX", "1nZ", "05r", "RH", "7oV", "4JT", "4dx", "5a8", "8l", "1Ow", "d5", "qT", "7LJ", "4iH", "4Gd", "66O", "2Ii", "08B",
|
||||
"Az", "0bH", "4Yf", "6lE", "6Bi", "43B", "z7", "oV", "bf", "0AT", "4zz", "6OY", "4A7", "4TV", "0ox", "LJ", "CK", "14P", "5Kv", "4N6",
|
||||
"543", "41s", "0NU", "mg", "22N", "u6", "4xK", "6Mh", "6cD", "4Vg", "0mI", "2Xj", "7Oa", "4jc", "0QM", "6w", "2JB", "I2", "4DO", "65d",
|
||||
"68T", "b7D", "06Y", "Qc", "dSm", "287", "4gS", "4r2", "7MP", "4hR", "276", "4F", "0h1", "09X", "b8E", "67U", "7nL", "4KN", "F3", "SR",
|
||||
"9v", "1Nm", "4eb", "6PA", "5kA", "6NC", "21e", "1Po", "X1", "MP", "74F", "4UL", "bbO", "79v", "0v3", "0cR", "8Df", "nL", "4c1", "42X",
|
||||
"4yQ", "4l0", "aM", "8Kg", "0lS", "Oa", "76w", "637", "4ZM", "6on", "BQ", "W0", "0OO", "2zl", "6AB", "40i", "4fM", "6Sn", "3xa", "k0",
|
||||
"07G", "2Fl", "69J", "4Ha", "4EQ", "4P0", "d5g", "83o", "0PS", "7i", "a0D", "bQN", "50U", "hbt", "8h", "1Os", "05v", "RL", "7oR", "4JP",
|
||||
"5WA", "66K", "2Im", "08F", "d1", "5X", "7LN", "4iL", "6Bm", "43F", "z3", "oR", "2Wo", "0bL", "4Yb", "6lA", "4A3", "4TR", "8fd", "LN",
|
||||
"bb", "0AP", "cPl", "aeO", "547", "41w", "0NQ", "mc", "CO", "14T", "5Kr", "4N2", "77i", "4Vc", "0mM", "2Xn", "22J", "u2", "4xO", "6Ml",
|
||||
"2JF", "I6", "4DK", "6qh", "7Oe", "4jg", "0QI", "6s", "1Y9", "1LX", "4gW", "4r6", "68P", "5YZ", "0rU", "Qg", "0h5", "1mu", "4Fz", "67Q",
|
||||
"7MT", "4hV", "0Sx", "4B", "9r", "1Ni", "4ef", "6PE", "7nH", "4KJ", "F7", "SV", "X5", "MT", "74B", "4UH", "5kE", "6NG", "cx", "1Pk",
|
||||
"0Mz", "nH", "4c5", "4vT", "4Xx", "79r", "0v7", "0cV", "0lW", "Oe", "5R9", "4Wy", "4yU", "4l4", "aI", "1RZ", "0OK", "ly", "6AF", "40m",
|
||||
"4ZI", "6oj", "BU", "W4", "265", "5E", "488", "4iQ", "b9F", "66V", "0i2", "1lr", "G0", "RQ", "7oO", "4JM", "4da", "6QB", "8u", "1On",
|
||||
"0PN", "7t", "7Nb", "aa0", "4EL", "64g", "2KA", "H1", "07Z", "0f3", "69W", "b6G", "4fP", "479", "dRn", "294", "22W", "8Jd", "4xR", "4m3",
|
||||
"77t", "624", "0mP", "Nb", "CR", "V3", "5Ko", "6nm", "ajS", "41j", "0NL", "3kN", "20f", "0AM", "4zc", "aeR", "6al", "4TO", "Y2", "LS",
|
||||
"Ac", "0bQ", "bcL", "78u", "4b2", "4wS", "8Ee", "oO", "7nU", "4KW", "04q", "SK", "9o", "1Nt", "51R", "6PX", "7MI", "4hK", "e6", "pW",
|
||||
"2Hj", "09A", "4Fg", "67L", "68M", "4If", "0rH", "Qz", "2iG", "j7", "4gJ", "6Ri", "7Ox", "4jz", "0QT", "6n", "1z8", "1oY", "4DV", "4Q7",
|
||||
"4ZT", "4O5", "BH", "0az", "0OV", "ld", "550", "40p", "4yH", "6Lk", "aT", "t5", "0lJ", "Ox", "6bG", "4Wd", "4Xe", "6mF", "2Vh", "0cK",
|
||||
"0Mg", "nU", "6Cj", "42A", "5kX", "6NZ", "ce", "1Pv", "2N9", "MI", "7pW", "4UU", "4Gy", "5B9", "0i6", "1lv", "1BZ", "5A", "7LW", "4iU",
|
||||
"4de", "6QF", "8q", "1Oj", "G4", "RU", "7oK", "4JI", "4EH", "64c", "2KE", "H5", "0PJ", "7p", "7Nf", "4kd", "4fT", "4s5", "2hY", "290",
|
||||
"0sV", "Pd", "5M8", "4Hx", "6cY", "4Vz", "0mT", "Nf", "1F8", "0Cx", "4xV", "4m7", "7Pd", "41n", "0NH", "mz", "CV", "V7", "5Kk", "6ni",
|
||||
"6ah", "4TK", "Y6", "LW", "20b", "0AI", "4zg", "6OD", "4b6", "4wW", "0Ly", "oK", "Ag", "0bU", "5IZ", "6lX", "9k", "1Np", "51V", "azN",
|
||||
"7nQ", "4KS", "04u", "SO", "2Hn", "09E", "4Fc", "67H", "7MM", "4hO", "e2", "pS", "2iC", "j3", "4gN", "6Rm", "68I", "4Ib", "06D", "2Go",
|
||||
"d4d", "82l", "4DR", "4Q3", "a1G", "bPM", "0QP", "6j", "0OR", "0Z3", "554", "40t", "4ZP", "4O1", "BL", "15W", "0lN", "2Ym", "6bC", "5GA",
|
||||
"4yL", "6Lo", "aP", "09", "0Mc", "nQ", "6Cn", "42E", "4Xa", "6mB", "2Vl", "0cO", "8gg", "MM", "7pS", "4UQ", "bAN", "adL", "ca", "1Pr",
|
||||
"G8", "RY", "7oG", "4JE", "4di", "6QJ", "2jd", "1Of", "0Rw", "5M", "480", "4iY", "4Gu", "5B5", "2Ix", "08S", "07R", "Ph", "5M4", "4Ht",
|
||||
"4fX", "471", "1X6", "1MW", "0PF", "st", "7Nj", "4kh", "4ED", "64o", "2KI", "H9", "CZ", "14A", "5Kg", "6ne", "7Ph", "41b", "0ND", "mv",
|
||||
"1F4", "0Ct", "4xZ", "6My", "5S6", "4Vv", "0mX", "Nj", "Ak", "0bY", "4Yw", "6lT", "6Bx", "43S", "0Lu", "oG", "bw", "0AE", "4zk", "6OH",
|
||||
"6ad", "4TG", "0oi", "2ZJ", "7MA", "4hC", "0Sm", "4W", "2Hb", "09I", "4Fo", "67D", "aTn", "b5d", "04y", "SC", "9g", "8WE", "4es", "6PP",
|
||||
"5o2", "4jr", "8XD", "6f", "1z0", "1oQ", "705", "65u", "68E", "4In", "06H", "Qr", "2iO", "1LM", "4gB", "6Ra", "5ia", "6Lc", "23E", "05",
|
||||
"0lB", "Op", "6bO", "4Wl", "c4F", "aEm", "1d2", "0ar", "8FF", "ll", "558", "40x", "5kP", "6NR", "cm", "344", "0ns", "MA", "74W", "bon",
|
||||
"4Xm", "6mN", "3FA", "0cC", "0Mo", "2xL", "6Cb", "42I", "4dm", "6QN", "8y", "1Ob", "05g", "2DL", "7oC", "4JA", "4Gq", "5B1", "d7G", "08W",
|
||||
"0Rs", "5I", "484", "bSn", "52u", "475", "1X2", "1MS", "07V", "Pl", "5M0", "4Hp", "5Ua", "64k", "2KM", "1nO", "0PB", "7x", "7Nn", "4kl",
|
||||
"7Pl", "41f", "8GX", "mr", "2UO", "14E", "5Kc", "6na", "5S2", "4Vr", "19u", "Nn", "1F0", "0Cp", "bBm", "ago", "ahn", "43W", "0Lq", "oC",
|
||||
"Ao", "16t", "4Ys", "6lP", "75I", "4TC", "0om", "2ZN", "bs", "0AA", "4zo", "6OL", "2Hf", "09M", "4Fk", "6sH", "7ME", "4hG", "0Si", "4S",
|
||||
"9c", "1Nx", "4ew", "6PT", "7nY", "bqh", "0pu", "SG", "1z4", "1oU", "4DZ", "65q", "5o6", "4jv", "0QX", "6b", "2iK", "1LI", "4gF", "6Re",
|
||||
"68A", "4Ij", "06L", "Qv", "0lF", "Ot", "6bK", "4Wh", "4yD", "6Lg", "aX", "01", "0OZ", "lh", "5q4", "4tt", "4ZX", "4O9", "BD", "0av",
|
||||
"0nw", "ME", "74S", "4UY", "5kT", "6NV", "ci", "1Pz", "0Mk", "nY", "6Cf", "42M", "4Xi", "6mJ", "2Vd", "0cG", "bL", "8Hf", "4zP", "4o1",
|
||||
"75v", "606", "0oR", "0z3", "AP", "T1", "4YL", "6lo", "6BC", "43h", "0LN", "2ym", "22d", "0CO", "4xa", "6MB", "6cn", "4VM", "0mc", "NQ",
|
||||
"Ca", "14z", "baN", "aDL", "7PS", "41Y", "8Gg", "mM", "247", "7G", "7NQ", "4kS", "com", "64T", "0k0", "1np", "E2", "PS", "69d", "4HO",
|
||||
"4fc", "7Ca", "2hn", "1Ml", "0RL", "5v", "avS", "4ib", "4GN", "66e", "2IC", "J3", "05X", "Rb", "aUO", "b4E", "4dR", "4q3", "8F", "8Vd",
|
||||
"4XV", "4M7", "1f8", "0cx", "0MT", "nf", "572", "42r", "5kk", "6Ni", "cV", "v7", "0nH", "Mz", "74l", "4Uf", "4Zg", "6oD", "2Tj", "0aI",
|
||||
"y6", "lW", "6Ah", "40C", "5iZ", "583", "ag", "0BU", "0ly", "OK", "4B6", "4WW", "7lW", "4IU", "06s", "QI", "0I6", "1Lv", "4gy", "5b9",
|
||||
"7OK", "4jI", "g4", "rU", "2Jh", "1oj", "4De", "65N", "7nf", "4Kd", "04B", "Sx", "2kE", "h5", "4eH", "6Pk", "5m8", "4hx", "0SV", "4l",
|
||||
"2HY", "09r", "4FT", "4S5", "5Q8", "4Tx", "0oV", "Ld", "bH", "0Az", "4zT", "4o5", "6BG", "43l", "0LJ", "ox", "AT", "T5", "4YH", "6lk",
|
||||
"6cj", "4VI", "0mg", "NU", "2vh", "0CK", "4xe", "6MF", "7PW", "4uU", "2n9", "mI", "Ce", "1pv", "5KX", "6nZ", "5UZ", "64P", "0k4", "1nt",
|
||||
"0Py", "7C", "7NU", "4kW", "4fg", "6SD", "2hj", "1Mh", "E6", "PW", "7mI", "4HK", "4GJ", "66a", "2IG", "J7", "0RH", "5r", "7Ld", "4if",
|
||||
"4dV", "4q7", "8B", "1OY", "0qT", "Rf", "7ox", "4Jz", "0MP", "nb", "576", "42v", "4XR", "4M3", "dll", "17U", "0nL", "3KN", "74h", "4Ub",
|
||||
"5ko", "6Nm", "cR", "v3", "y2", "lS", "6Al", "40G", "4Zc", "aER", "2Tn", "0aM", "18T", "OO", "4B2", "4WS", "bCL", "587", "ac", "0BQ",
|
||||
"0I2", "1Lr", "53T", "axL", "68z", "4IQ", "06w", "QM", "2Jl", "1on", "4Da", "65J", "7OO", "4jM", "g0", "6Y", "9X", "h1", "4eL", "6Po",
|
||||
"7nb", "aA0", "04F", "2Em", "d6f", "09v", "4FP", "4S1", "a3E", "bRO", "0SR", "4h", "AX", "T9", "4YD", "6lg", "6BK", "4wh", "0LF", "ot",
|
||||
"bD", "0Av", "4zX", "4o9", "5Q4", "4Tt", "0oZ", "Lh", "Ci", "14r", "5KT", "6nV", "ajh", "41Q", "0Nw", "mE", "22l", "0CG", "4xi", "6MJ",
|
||||
"6cf", "4VE", "0mk", "NY", "07a", "2FJ", "69l", "4HG", "4fk", "6SH", "2hf", "1Md", "0Pu", "7O", "7NY", "bQh", "4Ew", "6pT", "0k8", "1nx",
|
||||
"05P", "Rj", "5O6", "4Jv", "4dZ", "453", "8N", "1OU", "0RD", "qv", "7Lh", "4ij", "4GF", "66m", "2IK", "1lI", "5kc", "6Na", "21G", "27",
|
||||
"8gX", "Mr", "74d", "4Un", "bbm", "79T", "1f0", "0cp", "397", "nn", "5s2", "42z", "4ys", "6LP", "ao", "366", "0lq", "OC", "76U", "bml",
|
||||
"4Zo", "6oL", "Bs", "0aA", "0Om", "2zN", "7QA", "40K", "7OC", "4jA", "0Qo", "6U", "3ZA", "1ob", "4Dm", "65F", "68v", "b7f", "0rs", "QA",
|
||||
"dSO", "8UG", "4gq", "5b1", "5m0", "4hp", "8ZF", "4d", "1x2", "09z", "727", "67w", "7nn", "4Kl", "04J", "Sp", "9T", "1NO", "51i", "6Pc",
|
||||
"6BO", "43d", "0LB", "op", "2WM", "0bn", "5Ia", "6lc", "5Q0", "4Tp", "8fF", "Ll", "1D2", "0Ar", "cPN", "aem", "ajl", "41U", "0Ns", "mA",
|
||||
"Cm", "14v", "5KP", "6nR", "6cb", "4VA", "0mo", "2XL", "22h", "0CC", "4xm", "6MN", "4fo", "6SL", "2hb", "8TY", "07e", "2FN", "69h", "4HC",
|
||||
"4Es", "64X", "d5E", "83M", "0Pq", "7K", "a0f", "bQl", "50w", "457", "8J", "1OQ", "05T", "Rn", "5O2", "4Jr", "4GB", "66i", "2IO", "08d",
|
||||
"1Ba", "5z", "7Ll", "4in", "0nD", "Mv", "7ph", "4Uj", "5kg", "6Ne", "cZ", "23", "0MX", "nj", "5s6", "4vv", "4XZ", "6my", "1f4", "0ct",
|
||||
"0lu", "OG", "6bx", "5Gz", "4yw", "6LT", "ak", "0BY", "0Oi", "2zJ", "6Ad", "40O", "4Zk", "6oH", "Bw", "0aE", "2Jd", "1of", "4Di", "65B",
|
||||
"7OG", "4jE", "g8", "6Q", "2ix", "1Lz", "4gu", "5b5", "68r", "4IY", "0rw", "QE", "1x6", "1mW", "4FX", "4S9", "5m4", "4ht", "0SZ", "ph",
|
||||
"9P", "h9", "4eD", "6Pg", "7nj", "4Kh", "04N", "St", "22u", "375", "4xp", "598", "77V", "blo", "0mr", "1h2", "Cp", "14k", "5KM", "6nO",
|
||||
"7PB", "41H", "0Nn", "3kl", "20D", "34", "4zA", "6Ob", "6aN", "4Tm", "0oC", "Lq", "AA", "0bs", "bcn", "78W", "569", "43y", "384", "om",
|
||||
"9Kd", "5g", "5l3", "4is", "734", "66t", "1y1", "08y", "05I", "Rs", "7om", "4Jo", "4dC", "7AA", "8W", "1OL", "0Pl", "7V", "ats", "4kB",
|
||||
"4En", "64E", "2Kc", "1na", "07x", "PB", "69u", "b6e", "4fr", "5c2", "dRL", "8TD", "4Zv", "6oU", "Bj", "0aX", "0Ot", "lF", "6Ay", "40R",
|
||||
"4yj", "6LI", "av", "0BD", "0lh", "OZ", "6be", "4WF", "4XG", "6md", "2VJ", "0ci", "0ME", "nw", "6CH", "42c", "5kz", "6Nx", "cG", "1PT",
|
||||
"0nY", "Mk", "5P7", "4Uw", "5N5", "4Ku", "04S", "Si", "9M", "1NV", "4eY", "440", "7Mk", "4hi", "0SG", "pu", "2HH", "K8", "4FE", "67n",
|
||||
"68o", "4ID", "1", "QX", "2ie", "1Lg", "4gh", "6RK", "7OZ", "4jX", "0Qv", "6L", "2Jy", "3O9", "4Dt", "5A4", "4C9", "4VX", "0mv", "ND",
|
||||
"22q", "0CZ", "4xt", "6MW", "7PF", "41L", "x9", "mX", "Ct", "14o", "5KI", "6nK", "6aJ", "4Ti", "0oG", "Lu", "bY", "30", "4zE", "6Of",
|
||||
"5r5", "4wu", "380", "oi", "AE", "0bw", "4YY", "4L8", "5Wz", "66p", "1y5", "1lT", "0RY", "5c", "5l7", "4iw", "4dG", "6Qd", "8S", "1OH",
|
||||
"05M", "Rw", "7oi", "4Jk", "4Ej", "64A", "2Kg", "1ne", "0Ph", "7R", "7ND", "4kF", "4fv", "5c6", "0H9", "1My", "0st", "PF", "69q", "4HZ",
|
||||
"0Op", "lB", "ako", "40V", "4Zr", "6oQ", "Bn", "15u", "0ll", "2YO", "6ba", "4WB", "4yn", "6LM", "ar", "1Ra", "0MA", "ns", "6CL", "42g",
|
||||
"4XC", "79I", "2VN", "0cm", "8gE", "Mo", "5P3", "4Us", "bAl", "adn", "cC", "1PP", "9I", "1NR", "51t", "444", "5N1", "4Kq", "04W", "Sm",
|
||||
"2HL", "09g", "4FA", "67j", "7Mo", "4hm", "0SC", "4y", "2ia", "1Lc", "4gl", "6RO", "68k", "5Ya", "5", "2GM", "d4F", "82N", "4Dp", "5A0",
|
||||
"a1e", "bPo", "0Qr", "6H", "Cx", "14c", "5KE", "6nG", "7PJ", "4uH", "x5", "mT", "0V7", "0CV", "4xx", "590", "4C5", "4VT", "0mz", "NH",
|
||||
"AI", "16R", "4YU", "4L4", "561", "43q", "0LW", "oe", "bU", "w4", "4zI", "6Oj", "6aF", "4Te", "0oK", "Ly", "05A", "2Dj", "7oe", "4Jg",
|
||||
"4dK", "6Qh", "2jF", "i6", "0RU", "5o", "7Ly", "5yZ", "4GW", "4R6", "1y9", "08q", "07p", "PJ", "7mT", "4HV", "4fz", "6SY", "0H5", "1Mu",
|
||||
"f7", "sV", "7NH", "4kJ", "4Ef", "64M", "2Kk", "1ni", "4yb", "6LA", "23g", "0BL", "Z3", "OR", "6bm", "4WN", "c4d", "aEO", "Bb", "0aP",
|
||||
"8Fd", "lN", "4a3", "40Z", "5kr", "4n2", "cO", "8Ie", "0nQ", "Mc", "74u", "615", "4XO", "6ml", "2VB", "U2", "0MM", "2xn", "7Sa", "42k",
|
||||
"7Mc", "4ha", "0SO", "4u", "3Xa", "K0", "4FM", "67f", "aTL", "b5F", "0pS", "Sa", "9E", "8Wg", "4eQ", "448", "7OR", "4jP", "254", "6D",
|
||||
"0j3", "1os", "cnn", "65W", "68g", "4IL", "9", "QP", "2im", "1Lo", "53I", "6RC", "7PN", "41D", "x1", "mP", "2Um", "14g", "5KA", "6nC",
|
||||
"4C1", "4VP", "19W", "NL", "0V3", "0CR", "bBO", "594", "565", "43u", "0LS", "oa", "AM", "16V", "4YQ", "4L0", "6aB", "4Ta", "0oO", "2Zl",
|
||||
"bQ", "38", "4zM", "6On", "4dO", "6Ql", "2jB", "i2", "05E", "2Dn", "7oa", "4Jc", "4GS", "4R2", "d7e", "08u", "0RQ", "5k", "a2F", "bSL",
|
||||
"52W", "ayO", "0H1", "1Mq", "07t", "PN", "69y", "4HR", "4Eb", "64I", "2Ko", "1nm", "f3", "7Z", "7NL", "4kN", "Z7", "OV", "6bi", "4WJ",
|
||||
"4yf", "6LE", "az", "0BH", "0Ox", "lJ", "4a7", "4tV", "4Zz", "6oY", "Bf", "0aT", "0nU", "Mg", "74q", "5EZ", "5kv", "4n6", "cK", "1PX",
|
||||
"0MI", "2xj", "6CD", "42o", "4XK", "6mh", "2VF", "U6", "2HD", "K4", "4FI", "67b", "7Mg", "4he", "0SK", "4q", "9A", "1NZ", "4eU", "4p4",
|
||||
"5N9", "4Ky", "0pW", "Se", "0j7", "1ow", "4Dx", "5A8", "7OV", "4jT", "0Qz", "rH", "2ii", "1Lk", "4gd", "6RG", "68c", "4IH", "D5", "QT",
|
||||
"5Ls", "4I3", "F", "13U", "0IP", "jb", "536", "46v", "5oo", "6Jm", "gR", "r3", "0jL", "3ON", "6dA", "4Qb", "5NB", "aAR", "2Pn", "0eM",
|
||||
"0Ka", "hS", "6El", "44G", "49w", "abN", "ec", "0FQ", "8ae", "KO", "4F2", "4SS", "4X0", "4MQ", "02w", "UM", "0M2", "0XS", "57T", "a8D",
|
||||
"7KO", "4nM", "c0", "2Y", "2Nl", "1kn", "aJ1", "61J", "6zC", "aE0", "00F", "2Am", "yP", "l1", "4aL", "6To", "a7E", "58U", "0WR", "0h",
|
||||
"ZL", "84n", "4BP", "4W1", "fH", "0Ez", "5nu", "4k5", "5U8", "4Px", "0kV", "Hd", "ET", "P5", "5Mi", "6hk", "6FG", "47l", "0HJ", "kx",
|
||||
"dy", "0GK", "48m", "6IF", "6gj", "4RI", "0ig", "JU", "Ge", "0dW", "5OX", "5Z9", "4d4", "4qU", "1ZZ", "iI", "0Ty", "3C", "4z6", "4oW",
|
||||
"5QZ", "60P", "Yg", "0zU", "A6", "TW", "6yh", "4LK", "4bg", "6WD", "2lj", "0YI", "0VH", "1r", "6XE", "4mf", "4CJ", "62a", "2MG", "N7",
|
||||
"0uT", "Vf", "7kx", "4Nz", "5pw", "4u7", "xJ", "1KY", "0IT", "jf", "532", "46r", "5Lw", "4I7", "B", "0gx", "0jH", "Iz", "6dE", "4Qf",
|
||||
"5ok", "6Ji", "gV", "r7", "0Ke", "hW", "6Eh", "44C", "5NF", "6kD", "2Pj", "0eI", "0hy", "KK", "4F6", "4SW", "49s", "6HX", "eg", "0FU",
|
||||
"0M6", "0XW", "4cy", "5f9", "4X4", "4MU", "02s", "UI", "Xy", "1kj", "5PD", "61N", "7KK", "4nI", "c4", "vU", "yT", "l5", "4aH", "6Tk",
|
||||
"6zG", "4Od", "00B", "Wx", "ZH", "0yz", "4BT", "4W5", "5i8", "4lx", "0WV", "0l", "71v", "646", "0kR", "3NP", "fL", "8Lf", "5nq", "4k1",
|
||||
"6FC", "47h", "0HN", "29e", "EP", "P1", "5Mm", "6ho", "6gn", "4RM", "0ic", "JQ", "26d", "0GO", "48i", "6IB", "4d0", "45Y", "8Cg", "iM",
|
||||
"Ga", "0dS", "beN", "hYu", "ckm", "60T", "Yc", "0zQ", "207", "3G", "4z2", "4oS", "4bc", "7Ga", "2ln", "0YM", "A2", "TS", "6yl", "4LO",
|
||||
"4CN", "62e", "2MC", "N3", "0VL", "1v", "6XA", "4mb", "5ps", "4u3", "xN", "8Rd", "01X", "Vb", "aQO", "b0E", "5og", "6Je", "gZ", "63",
|
||||
"0jD", "Iv", "6dI", "4Qj", "7l9", "6iy", "N", "0gt", "0IX", "jj", "5w6", "4rv", "5mV", "5x7", "ek", "0FY", "0hu", "KG", "6fx", "5Cz",
|
||||
"5NJ", "6kH", "Fw", "0eE", "92", "3nk", "6Ed", "44O", "7KG", "4nE", "c8", "2Q", "Xu", "1kf", "5PH", "61B", "4X8", "4MY", "0vw", "UE",
|
||||
"2mx", "1Hz", "4cu", "5f5", "5i4", "4lt", "0WZ", "th", "ZD", "0yv", "4BX", "4W9", "6zK", "4Oh", "00N", "Wt", "yX", "l9", "4aD", "6Tg",
|
||||
"2SM", "0fn", "5Ma", "6hc", "6FO", "47d", "0HB", "kp", "24Y", "0Er", "bDo", "aam", "5U0", "4Pp", "8bF", "Hl", "Gm", "10v", "5OP", "5Z1",
|
||||
"anl", "45U", "0Js", "iA", "dq", "0GC", "48e", "6IN", "6gb", "4RA", "0io", "3Lm", "03e", "2BN", "7iA", "4LC", "4bo", "6WL", "zs", "0YA",
|
||||
"0Tq", "3K", "a4f", "bUl", "4As", "5D3", "Yo", "87M", "01T", "Vn", "5K2", "4Nr", "54w", "417", "xB", "1KQ", "1Fa", "1z", "6XM", "4mn",
|
||||
"4CB", "62i", "2MO", "0xl", "1za", "Ir", "6dM", "4Qn", "5oc", "6Ja", "25G", "67", "9Pe", "jn", "5w2", "46z", "bfm", "aCo", "J", "0gp",
|
||||
"0hq", "KC", "72U", "bil", "5mR", "5x3", "eo", "326", "96", "3no", "7UA", "44K", "5NN", "6kL", "Fs", "0eA", "Xq", "1kb", "5PL", "61F",
|
||||
"7KC", "4nA", "0Uo", "2U", "39U", "8QG", "4cq", "5f1", "aRl", "796", "0vs", "UA", "2LQ", "0yr", "767", "63w", "5i0", "4lp", "9Ng", "0d",
|
||||
"2oM", "0Zn", "55i", "6Tc", "6zO", "4Ol", "00J", "Wp", "6FK", "4sh", "0HF", "kt", "EX", "P9", "5Me", "6hg", "5U4", "4Pt", "0kZ", "Hh",
|
||||
"fD", "0Ev", "5ny", "4k9", "4d8", "45Q", "0Jw", "iE", "Gi", "10r", "5OT", "5Z5", "6gf", "4RE", "0ik", "JY", "du", "0GG", "48a", "6IJ",
|
||||
"4bk", "6WH", "zw", "0YE", "03a", "2BJ", "6yd", "4LG", "4Aw", "5D7", "Yk", "0zY", "0Tu", "3O", "6Zx", "bUh", "54s", "413", "xF", "1KU",
|
||||
"01P", "Vj", "5K6", "4Nv", "4CF", "62m", "2MK", "0xh", "0VD", "uv", "6XI", "4mj", "5NS", "6kQ", "Fn", "11u", "0Kp", "hB", "aoo", "44V",
|
||||
"49f", "6HM", "er", "1Va", "0hl", "3Mn", "6fa", "4SB", "5Lb", "7yA", "W", "0gm", "0IA", "js", "6GL", "46g", "bEl", "hyW", "gC", "0Dq",
|
||||
"8cE", "Io", "5T3", "4Qs", "5J1", "4Oq", "00W", "Wm", "yA", "0Zs", "55t", "404", "6YN", "4lm", "0WC", "0y", "2LL", "0yo", "4BA", "63j",
|
||||
"6xc", "bws", "02f", "2CM", "2ma", "0XB", "4cl", "6VO", "a5e", "bTo", "0Ur", "2H", "Xl", "86N", "5PQ", "5E0", "dh", "0GZ", "5lU", "5y4",
|
||||
"4G9", "4RX", "0iv", "JD", "Gt", "0dF", "5OI", "6jK", "6Dg", "45L", "81", "iX", "fY", "70", "5nd", "6Kf", "6eJ", "4Pi", "0kG", "Hu",
|
||||
"EE", "0fw", "5Mx", "4H8", "5v5", "4su", "1Xz", "ki", "0VY", "1c", "5h7", "4mw", "5Sz", "62p", "2MV", "0xu", "01M", "Vw", "7ki", "4Nk",
|
||||
"54n", "6Ud", "2nJ", "1KH", "0Th", "3R", "6Ze", "4oF", "4Aj", "60A", "Yv", "0zD", "0wt", "TF", "6yy", "4LZ", "4bv", "5g6", "zj", "0YX",
|
||||
"0Kt", "hF", "6Ey", "44R", "5NW", "6kU", "Fj", "0eX", "0hh", "KZ", "6fe", "4SF", "49b", "6HI", "ev", "0FD", "0IE", "jw", "6GH", "46c",
|
||||
"5Lf", "6id", "S", "0gi", "0jY", "Ik", "5T7", "4Qw", "5oz", "6Jx", "gG", "0Du", "yE", "0Zw", "4aY", "400", "5J5", "4Ou", "00S", "Wi",
|
||||
"ZY", "O8", "4BE", "63n", "6YJ", "4li", "0WG", "tu", "2me", "0XF", "4ch", "6VK", "6xg", "4MD", "02b", "UX", "Xh", "3K9", "5PU", "5E4",
|
||||
"7KZ", "4nX", "0Uv", "2L", "73V", "bho", "0ir", "1l2", "dl", "335", "48x", "5y0", "6Dc", "45H", "85", "3ol", "Gp", "0dB", "5OM", "6jO",
|
||||
"6eN", "4Pm", "0kC", "Hq", "24D", "74", "bDr", "6Kb", "529", "47y", "8AG", "km", "EA", "0fs", "bgn", "aBl", "774", "62t", "199", "0xq",
|
||||
"9Od", "1g", "5h3", "4ms", "54j", "7EA", "2nN", "1KL", "01I", "Vs", "7km", "4No", "4An", "60E", "Yr", "1ja", "0Tl", "3V", "6Za", "4oB",
|
||||
"4br", "5g2", "zn", "8PD", "03x", "TB", "aSo", "785", "49n", "6HE", "ez", "0FH", "0hd", "KV", "6fi", "4SJ", "bdI", "6kY", "Ff", "0eT",
|
||||
"0Kx", "hJ", "4e7", "4pV", "5ov", "4j6", "gK", "0Dy", "0jU", "Ig", "6dX", "5AZ", "5Lj", "6ih", "DW", "Q6", "0II", "28b", "6GD", "46o",
|
||||
"6YF", "4le", "0WK", "0q", "ZU", "O4", "4BI", "63b", "5J9", "4Oy", "0tW", "We", "yI", "1JZ", "4aU", "4t4", "7KV", "4nT", "0Uz", "vH",
|
||||
"Xd", "1kw", "5PY", "5E8", "6xk", "4MH", "02n", "UT", "2mi", "0XJ", "4cd", "6VG", "2Qm", "0dN", "5OA", "6jC", "6Do", "45D", "89", "iP",
|
||||
"0R3", "0GR", "48t", "acM", "4G1", "4RP", "94O", "JL", "EM", "12V", "5Mp", "4H0", "525", "47u", "0HS", "ka", "fQ", "78", "5nl", "6Kn",
|
||||
"6eB", "4Pa", "0kO", "3NM", "01E", "3PO", "7ka", "4Nc", "54f", "6Ul", "xS", "m2", "0VQ", "1k", "a6F", "59V", "4CS", "4V2", "195", "85m",
|
||||
"03t", "TN", "4Y3", "4LR", "56W", "a9G", "zb", "0YP", "b3", "3Z", "6Zm", "4oN", "4Ab", "60I", "2Oo", "0zL", "1xA", "KR", "6fm", "4SN",
|
||||
"49j", "6HA", "27g", "0FL", "8Bd", "hN", "4e3", "44Z", "bdM", "aAO", "Fb", "0eP", "0jQ", "Ic", "70u", "655", "5or", "4j2", "gO", "8Me",
|
||||
"0IM", "28f", "7Wa", "46k", "5Ln", "6il", "DS", "Q2", "ZQ", "O0", "4BM", "63f", "6YB", "4la", "0WO", "0u", "yM", "8Sg", "4aQ", "408",
|
||||
"aPL", "b1F", "0tS", "Wa", "0n3", "1ks", "bzO", "61W", "7KR", "4nP", "214", "2D", "2mm", "0XN", "57I", "6VC", "6xo", "4ML", "02j", "UP",
|
||||
"6Dk", "4qH", "0Jf", "iT", "Gx", "0dJ", "5OE", "6jG", "4G5", "4RT", "0iz", "JH", "dd", "0GV", "48p", "5y8", "521", "47q", "0HW", "ke",
|
||||
"EI", "12R", "5Mt", "4H4", "6eF", "4Pe", "0kK", "Hy", "fU", "s4", "5nh", "6Kj", "54b", "6Uh", "xW", "m6", "01A", "3PK", "7ke", "4Ng",
|
||||
"4CW", "4V6", "191", "0xy", "0VU", "1o", "6XX", "59R", "4bz", "6WY", "zf", "0YT", "03p", "TJ", "4Y7", "4LV", "4Af", "60M", "Yz", "0zH",
|
||||
"b7", "wV", "6Zi", "4oJ", "5H3", "4Ms", "02U", "Uo", "2mR", "0Xq", "57v", "426", "7Km", "4no", "0UA", "vs", "2NN", "1kL", "5Pb", "61h",
|
||||
"6za", "4OB", "00d", "2AO", "yr", "1Ja", "4an", "6TM", "a7g", "58w", "0Wp", "0J", "Zn", "84L", "4Br", "5G2", "5LQ", "5Y0", "d", "13w",
|
||||
"0Ir", "1L2", "amm", "46T", "5oM", "6JO", "gp", "0DB", "0jn", "3Ol", "6dc", "5Aa", "bdr", "6kb", "2PL", "0eo", "0KC", "hq", "6EN", "44e",
|
||||
"49U", "abl", "eA", "0Fs", "8aG", "Km", "5V1", "4Sq", "1Dz", "3a", "5j5", "4ou", "4AY", "4T8", "YE", "0zw", "03O", "Tu", "6yJ", "4Li",
|
||||
"4bE", "6Wf", "zY", "o8", "0Vj", "1P", "6Xg", "4mD", "4Ch", "62C", "2Me", "0xF", "0uv", "VD", "7kZ", "4NX", "5pU", "5e4", "xh", "3k9",
|
||||
"fj", "0EX", "5nW", "6KU", "6ey", "4PZ", "0kt", "HF", "Ev", "0fD", "5MK", "6hI", "6Fe", "47N", "0Hh", "kZ", "26B", "52", "48O", "6Id",
|
||||
"6gH", "4Rk", "0iE", "Jw", "GG", "0du", "5Oz", "6jx", "5t7", "4qw", "0JY", "ik", "2mV", "0Xu", "57r", "422", "5H7", "4Mw", "02Q", "Uk",
|
||||
"2NJ", "1kH", "5Pf", "61l", "7Ki", "4nk", "0UE", "vw", "yv", "0ZD", "4aj", "6TI", "6ze", "4OF", "0th", "WZ", "Zj", "0yX", "4Bv", "5G6",
|
||||
"6Yy", "4lZ", "0Wt", "0N", "0Iv", "jD", "4g9", "46P", "5LU", "5Y4", "Dh", "0gZ", "0jj", "IX", "6dg", "4QD", "5oI", "6JK", "gt", "0DF",
|
||||
"0KG", "hu", "6EJ", "44a", "5Nd", "6kf", "FY", "S8", "1xz", "Ki", "5V5", "4Su", "49Q", "4h8", "eE", "0Fw", "756", "60v", "YA", "0zs",
|
||||
"9Mf", "3e", "5j1", "4oq", "4bA", "6Wb", "2lL", "0Yo", "03K", "Tq", "6yN", "4Lm", "4Cl", "62G", "2Ma", "0xB", "0Vn", "1T", "6Xc", "59i",
|
||||
"54Y", "5e0", "xl", "8RF", "01z", "1p2", "aQm", "b0g", "71T", "bjm", "0kp", "HB", "fn", "317", "5nS", "6KQ", "6Fa", "47J", "0Hl", "29G",
|
||||
"Er", "12i", "5MO", "6hM", "6gL", "4Ro", "0iA", "Js", "26F", "56", "48K", "7YA", "5t3", "4qs", "8CE", "io", "GC", "0dq", "bel", "hYW",
|
||||
"7Ke", "4ng", "0UI", "2s", "XW", "M6", "5Pj", "6uh", "6xX", "6m9", "0vU", "Ug", "2mZ", "0Xy", "4cW", "4v6", "4y7", "4lV", "0Wx", "0B",
|
||||
"Zf", "0yT", "4Bz", "63Q", "6zi", "4OJ", "B7", "WV", "yz", "0ZH", "4af", "6TE", "5oE", "6JG", "gx", "0DJ", "0jf", "IT", "6dk", "4QH",
|
||||
"5LY", "5Y8", "l", "0gV", "0Iz", "jH", "4g5", "4rT", "5mt", "4h4", "eI", "1VZ", "0hW", "Ke", "5V9", "4Sy", "5Nh", "6kj", "FU", "S4",
|
||||
"0KK", "hy", "6EF", "44m", "03G", "2Bl", "6yB", "4La", "4bM", "6Wn", "zQ", "o0", "0TS", "3i", "a4D", "bUN", "4AQ", "4T0", "YM", "87o",
|
||||
"01v", "VL", "7kR", "4NP", "54U", "hft", "0N3", "1Ks", "0Vb", "1X", "6Xo", "4mL", "5SA", "62K", "2Mm", "0xN", "2So", "0fL", "5MC", "6hA",
|
||||
"6Fm", "47F", "1XA", "kR", "fb", "0EP", "bDM", "aaO", "4E3", "4PR", "8bd", "HN", "GO", "10T", "5Or", "4J2", "507", "45w", "0JQ", "ic",
|
||||
"dS", "q2", "48G", "6Il", "73i", "4Rc", "0iM", "3LO", "XS", "M2", "5Pn", "61d", "7Ka", "4nc", "0UM", "2w", "39w", "8Qe", "4cS", "4v2",
|
||||
"aRN", "b3D", "02Y", "Uc", "Zb", "0yP", "bxM", "63U", "4y3", "4lR", "236", "0F", "2oo", "0ZL", "4ab", "6TA", "6zm", "4ON", "B3", "WR",
|
||||
"0jb", "IP", "6do", "4QL", "5oA", "6JC", "25e", "0DN", "9PG", "jL", "4g1", "46X", "686", "aCM", "h", "0gR", "0hS", "Ka", "72w", "677",
|
||||
"49Y", "4h0", "eM", "8Og", "0KO", "3nM", "6EB", "44i", "5Nl", "6kn", "FQ", "S0", "4bI", "6Wj", "zU", "o4", "03C", "Ty", "6yF", "4Le",
|
||||
"4AU", "4T4", "YI", "1jZ", "0TW", "3m", "5j9", "4oy", "54Q", "5e8", "xd", "1Kw", "01r", "VH", "7kV", "4NT", "4Cd", "62O", "2Mi", "0xJ",
|
||||
"0Vf", "uT", "6Xk", "4mH", "6Fi", "47B", "0Hd", "kV", "Ez", "0fH", "5MG", "6hE", "4E7", "4PV", "0kx", "HJ", "ff", "0ET", "bDI", "6KY",
|
||||
"503", "45s", "0JU", "ig", "GK", "0dy", "5Ov", "4J6", "6gD", "4Rg", "0iI", "3LK", "dW", "q6", "48C", "6Ih", "4Z2", "4OS", "00u", "WO",
|
||||
"yc", "0ZQ", "55V", "hgw", "6Yl", "4lO", "a2", "tS", "2Ln", "0yM", "4Bc", "63H", "6xA", "4Mb", "02D", "2Co", "2mC", "n3", "4cN", "6Vm",
|
||||
"a5G", "bTM", "0UP", "2j", "XN", "86l", "5Ps", "4U3", "5Nq", "4K1", "FL", "11W", "0KR", "3nP", "514", "44t", "49D", "6Ho", "eP", "49",
|
||||
"0hN", "3ML", "6fC", "5CA", "aV1", "6iB", "u", "0gO", "0Ic", "jQ", "6Gn", "46E", "bEN", "hyu", "ga", "0DS", "8cg", "IM", "4D0", "4QQ",
|
||||
"1FZ", "1A", "4x4", "4mU", "4Cy", "5F9", "0m6", "0xW", "C4", "VU", "7kK", "4NI", "54L", "6UF", "xy", "1Kj", "0TJ", "3p", "6ZG", "4od",
|
||||
"4AH", "60c", "YT", "L5", "0wV", "Td", "5I8", "4Lx", "4bT", "4w5", "zH", "0Yz", "dJ", "0Gx", "5lw", "4i7", "6gY", "4Rz", "0iT", "Jf",
|
||||
"GV", "R7", "5Ok", "6ji", "6DE", "45n", "0JH", "iz", "24b", "0EI", "5nF", "6KD", "6eh", "4PK", "0ke", "HW", "Eg", "0fU", "5MZ", "6hX",
|
||||
"4f6", "4sW", "0Hy", "kK", "yg", "0ZU", "55R", "6TX", "4Z6", "4OW", "00q", "WK", "2Lj", "0yI", "4Bg", "63L", "6Yh", "4lK", "a6", "tW",
|
||||
"2mG", "n7", "4cJ", "6Vi", "6xE", "4Mf", "0vH", "Uz", "XJ", "1kY", "5Pw", "4U7", "7Kx", "4nz", "0UT", "2n", "0KV", "hd", "510", "44p",
|
||||
"5Nu", "4K5", "FH", "0ez", "0hJ", "Kx", "6fG", "4Sd", "5mi", "6Hk", "eT", "p5", "0Ig", "jU", "6Gj", "46A", "5LD", "6iF", "q", "0gK",
|
||||
"1zZ", "II", "4D4", "4QU", "5oX", "5z9", "ge", "0DW", "byN", "62V", "0m2", "0xS", "225", "1E", "4x0", "4mQ", "54H", "6UB", "2nl", "1Kn",
|
||||
"C0", "VQ", "7kO", "4NM", "4AL", "60g", "YP", "L1", "0TN", "3t", "6ZC", "ae0", "4bP", "439", "zL", "8Pf", "03Z", "0b3", "aSM", "b2G",
|
||||
"73t", "664", "0iP", "Jb", "dN", "8Nd", "48Z", "4i3", "6DA", "45j", "0JL", "3oN", "GR", "R3", "5Oo", "6jm", "6el", "4PO", "0ka", "HS",
|
||||
"24f", "0EM", "5nB", "aaR", "4f2", "4sS", "8Ae", "kO", "Ec", "0fQ", "695", "aBN", "6Yd", "4lG", "0Wi", "0S", "Zw", "0yE", "4Bk", "6wH",
|
||||
"6zx", "buh", "0tu", "WG", "yk", "0ZY", "4aw", "5d7", "5k6", "4nv", "0UX", "2b", "XF", "1kU", "741", "61q", "6xI", "4Mj", "02L", "Uv",
|
||||
"2mK", "0Xh", "4cF", "6Ve", "49L", "6Hg", "eX", "41", "0hF", "Kt", "6fK", "4Sh", "5Ny", "4K9", "FD", "0ev", "0KZ", "hh", "5u4", "4pt",
|
||||
"5oT", "5z5", "gi", "1Tz", "0jw", "IE", "4D8", "4QY", "5LH", "6iJ", "Du", "0gG", "0Ik", "jY", "6Gf", "46M", "01g", "3Pm", "7kC", "4NA",
|
||||
"54D", "6UN", "xq", "1Kb", "0Vs", "1I", "a6d", "59t", "4Cq", "5F1", "d3G", "85O", "03V", "Tl", "5I0", "4Lp", "56u", "435", "2lQ", "0Yr",
|
||||
"0TB", "3x", "6ZO", "4ol", "5Qa", "60k", "2OM", "0zn", "2QO", "0dl", "5Oc", "6ja", "6DM", "45f", "1Za", "ir", "dB", "0Gp", "48V", "aco",
|
||||
"5W2", "4Rr", "94m", "Jn", "Eo", "12t", "5MR", "5X3", "aln", "47W", "0Hq", "kC", "fs", "0EA", "5nN", "6KL", "71I", "4PC", "0km", "3No",
|
||||
"Zs", "0yA", "4Bo", "63D", "7IA", "4lC", "0Wm", "0W", "yo", "8SE", "4as", "5d3", "aPn", "b1d", "00y", "WC", "XB", "1kQ", "745", "61u",
|
||||
"5k2", "4nr", "9Le", "2f", "2mO", "0Xl", "4cB", "6Va", "6xM", "4Mn", "02H", "Ur", "0hB", "Kp", "6fO", "4Sl", "49H", "6Hc", "27E", "45",
|
||||
"8BF", "hl", "518", "44x", "bdo", "aAm", "2PQ", "0er", "0js", "IA", "70W", "bkn", "5oP", "5z1", "gm", "304", "0Io", "28D", "6Gb", "46I",
|
||||
"5LL", "6iN", "y", "0gC", "5pH", "6UJ", "xu", "1Kf", "C8", "VY", "7kG", "4NE", "4Cu", "5F5", "2Mx", "1hz", "0Vw", "1M", "4x8", "4mY",
|
||||
"4bX", "431", "zD", "0Yv", "03R", "Th", "5I4", "4Lt", "4AD", "60o", "YX", "L9", "0TF", "wt", "6ZK", "4oh", "6DI", "45b", "0JD", "iv",
|
||||
"GZ", "0dh", "5Og", "6je", "5W6", "4Rv", "0iX", "Jj", "dF", "0Gt", "48R", "6Iy", "6Fx", "47S", "0Hu", "kG", "Ek", "0fY", "5MV", "5X7",
|
||||
"6ed", "4PG", "0ki", "3Nk", "fw", "0EE", "5nJ", "6KH", "356", "bo", "6OP", "4zs", "bnl", "75U", "LC", "0oq", "0bA", "As", "6lL", "4Yo",
|
||||
"43K", "7RA", "2yN", "0Lm", "17", "22G", "6Ma", "4xB", "4Vn", "6cM", "Nr", "19i", "14Y", "CB", "aDo", "bam", "41z", "5p2", "mn", "8GD",
|
||||
"7d", "8YF", "4kp", "5n0", "64w", "717", "1nS", "2KQ", "Pp", "07J", "4Hl", "69G", "6Sc", "52i", "1MO", "2hM", "5U", "0Ro", "4iA", "7LC",
|
||||
"66F", "4Gm", "08K", "3YA", "RA", "0qs", "b4f", "aUl", "5a1", "4dq", "8VG", "8e", "6mV", "4Xu", "17r", "022", "nE", "0Mw", "42Q", "4c8",
|
||||
"6NJ", "5kH", "1Pf", "cu", "MY", "X8", "4UE", "74O", "6og", "4ZD", "W9", "BX", "lt", "0OF", "4th", "6AK", "4l9", "4yX", "0Bv", "aD",
|
||||
"Oh", "0lZ", "4Wt", "5R4", "4Iv", "5L6", "Qj", "06P", "1LU", "1Y4", "463", "4gZ", "4jj", "7Oh", "rv", "0QD", "1oI", "2JK", "65m", "4DF",
|
||||
"4KG", "7nE", "2EJ", "04a", "1Nd", "2kf", "6PH", "4ek", "5xz", "492", "4O", "0Su", "09Q", "0h8", "5C7", "4Fw", "5Dz", "6ax", "LG", "0ou",
|
||||
"0AY", "bk", "6OT", "4zw", "43O", "6Bd", "2yJ", "0Li", "0bE", "Aw", "6lH", "4Yk", "4Vj", "6cI", "Nv", "0mD", "13", "22C", "6Me", "4xF",
|
||||
"4uv", "5p6", "mj", "0NX", "1pU", "CF", "6ny", "7k9", "4P9", "4EX", "1nW", "2KU", "sh", "0PZ", "4kt", "5n4", "6Sg", "4fD", "k9", "2hI",
|
||||
"Pt", "07N", "4Hh", "69C", "66B", "4Gi", "08O", "2Id", "5Q", "d8", "4iE", "7LG", "5a5", "4du", "1Oz", "8a", "RE", "0qw", "4JY", "aUh",
|
||||
"nA", "0Ms", "42U", "ail", "6mR", "4Xq", "17v", "026", "3Km", "0no", "4UA", "74K", "6NN", "5kL", "1Pb", "cq", "lp", "0OB", "40d", "6AO",
|
||||
"6oc", "5Ja", "0an", "2TM", "Ol", "18w", "4Wp", "5R0", "afm", "bCo", "0Br", "1G2", "1LQ", "1Y0", "467", "53w", "4Ir", "5L2", "Qn", "06T",
|
||||
"1oM", "2JO", "65i", "4DB", "4jn", "7Ol", "6z", "1Aa", "8WY", "2kb", "6PL", "4eo", "4KC", "7nA", "2EN", "04e", "09U", "d6E", "5C3", "4Fs",
|
||||
"bRl", "496", "4K", "0Sq", "0bI", "2Wj", "6lD", "4Yg", "43C", "6Bh", "oW", "z6", "0AU", "bg", "6OX", "5jZ", "4TW", "4A6", "LK", "0oy",
|
||||
"14Q", "CJ", "4N7", "5Kw", "41r", "542", "mf", "0NT", "u7", "22O", "6Mi", "4xJ", "4Vf", "6cE", "Nz", "0mH", "Px", "07B", "4Hd", "69O",
|
||||
"6Sk", "4fH", "k5", "2hE", "7l", "0PV", "4kx", "5n8", "4P5", "4ET", "83j", "2KY", "RI", "05s", "4JU", "7oW", "5a9", "4dy", "1Ov", "8m",
|
||||
"qU", "d4", "4iI", "7LK", "66N", "4Ge", "08C", "2Ih", "6NB", "a59", "1Pn", "21d", "MQ", "X0", "4UM", "74G", "79w", "bbN", "0cS", "0v2",
|
||||
"nM", "8Dg", "42Y", "4c0", "4l1", "4yP", "8Kf", "aL", "0y3", "0lR", "636", "76v", "6oo", "4ZL", "W1", "BP", "2zm", "0ON", "40h", "6AC",
|
||||
"4jb", "auS", "6v", "0QL", "I3", "2JC", "65e", "4DN", "b7E", "68U", "Qb", "06X", "286", "dSl", "4r3", "4gR", "4hS", "7MQ", "4G", "277",
|
||||
"09Y", "0h0", "67T", "b8D", "4KO", "7nM", "SS", "F2", "1Nl", "9w", "azR", "4ec", "43G", "6Bl", "oS", "z2", "0bM", "2Wn", "78i", "4Yc",
|
||||
"4TS", "4A2", "LO", "8fe", "0AQ", "bc", "aeN", "cPm", "41v", "546", "mb", "0NP", "14U", "CN", "4N3", "5Ks", "4Vb", "6cA", "2Xo", "0mL",
|
||||
"u3", "22K", "6Mm", "4xN", "6So", "4fL", "k1", "2hA", "2Fm", "07F", "5XA", "69K", "4P1", "4EP", "83n", "d5f", "7h", "0PR", "bQO", "a0E",
|
||||
"hbu", "50T", "1Or", "8i", "RM", "05w", "4JQ", "7oS", "66J", "4Ga", "08G", "2Il", "5Y", "d0", "4iM", "7LO", "MU", "X4", "4UI", "74C",
|
||||
"6NF", "5kD", "1Pj", "cy", "nI", "2m9", "4vU", "4c4", "6mZ", "4Xy", "0cW", "0v6", "Od", "0lV", "4Wx", "5R8", "4l5", "4yT", "0Bz", "aH",
|
||||
"lx", "0OJ", "40l", "6AG", "6ok", "4ZH", "W5", "BT", "I7", "2JG", "65a", "4DJ", "4jf", "7Od", "6r", "0QH", "1LY", "1Y8", "4r7", "4gV",
|
||||
"4Iz", "68Q", "Qf", "0rT", "1mt", "0h4", "67P", "5VZ", "4hW", "7MU", "4C", "0Sy", "1Nh", "9s", "6PD", "4eg", "4KK", "7nI", "SW", "F6",
|
||||
"8Je", "22V", "4m2", "4xS", "625", "77u", "Nc", "0mQ", "V2", "CS", "6nl", "5Kn", "41k", "7Pa", "3kO", "0NM", "0AL", "20g", "6OA", "4zb",
|
||||
"4TN", "6am", "LR", "Y3", "0bP", "Ab", "78t", "bcM", "43Z", "4b3", "oN", "8Ed", "5D", "264", "4iP", "489", "66W", "b9G", "08Z", "0i3",
|
||||
"RP", "G1", "4JL", "7oN", "6QC", "50I", "1Oo", "8t", "7u", "0PO", "4ka", "7Nc", "64f", "4EM", "H0", "963", "Pa", "0sS", "b6F", "69V",
|
||||
"478", "4fQ", "295", "dRo", "4O4", "4ZU", "15R", "BI", "le", "0OW", "40q", "551", "6Lj", "4yI", "t4", "aU", "Oy", "0lK", "4We", "6bF",
|
||||
"6mG", "4Xd", "0cJ", "2Vi", "nT", "0Mf", "4vH", "6Ck", "adI", "5kY", "1Pw", "cd", "MH", "0nz", "4UT", "7pV", "4KV", "7nT", "SJ", "04p",
|
||||
"1Nu", "9n", "6PY", "4ez", "4hJ", "7MH", "pV", "e7", "1mi", "2Hk", "67M", "4Ff", "4Ig", "68L", "2Gj", "06A", "j6", "2iF", "6Rh", "4gK",
|
||||
"5zZ", "7Oy", "6o", "0QU", "1oX", "1z9", "4Q6", "4DW", "5FZ", "6cX", "Ng", "0mU", "0Cy", "1F9", "4m6", "4xW", "41o", "7Pe", "3kK", "0NI",
|
||||
"V6", "CW", "6nh", "5Kj", "4TJ", "6ai", "LV", "Y7", "0AH", "bz", "6OE", "4zf", "4wV", "4b7", "oJ", "0Lx", "0bT", "Af", "6lY", "4Yz",
|
||||
"5B8", "4Gx", "1lw", "0i7", "qH", "0Rz", "4iT", "7LV", "6QG", "4dd", "1Ok", "8p", "RT", "G5", "4JH", "7oJ", "64b", "4EI", "H4", "2KD",
|
||||
"7q", "0PK", "4ke", "7Ng", "4s4", "4fU", "1MZ", "2hX", "Pe", "0sW", "4Hy", "5M9", "la", "0OS", "40u", "555", "4O0", "4ZQ", "15V", "BM",
|
||||
"2Yl", "0lO", "4Wa", "6bB", "6Ln", "4yM", "08", "aQ", "nP", "0Mb", "42D", "6Co", "6mC", "5HA", "0cN", "2Vm", "ML", "8gf", "4UP", "74Z",
|
||||
"adM", "bAO", "1Ps", "0U3", "1Nq", "9j", "azO", "51W", "4KR", "7nP", "SN", "04t", "09D", "2Ho", "67I", "4Fb", "4hN", "7ML", "4Z", "e3",
|
||||
"j2", "2iB", "6Rl", "4gO", "4Ic", "68H", "2Gn", "06E", "82m", "d4e", "4Q2", "4DS", "bPL", "a1F", "6k", "0QQ", "1pH", "2UJ", "6nd", "5Kf",
|
||||
"41c", "7Pi", "mw", "0NE", "0Cu", "1F5", "6Mx", "5hz", "4Vw", "5S7", "Nk", "0mY", "0bX", "Aj", "6lU", "4Yv", "43R", "6By", "oF", "0Lt",
|
||||
"0AD", "bv", "6OI", "4zj", "4TF", "6ae", "LZ", "0oh", "RX", "G9", "4JD", "7oF", "6QK", "4dh", "1Og", "2je", "5L", "0Rv", "4iX", "481",
|
||||
"5B4", "4Gt", "08R", "2Iy", "Pi", "07S", "4Hu", "5M5", "470", "4fY", "1MV", "1X7", "su", "0PG", "4ki", "7Nk", "64n", "4EE", "H8", "2KH",
|
||||
"6Lb", "4yA", "04", "23D", "Oq", "0lC", "4Wm", "6bN", "aEl", "c4G", "0as", "BA", "lm", "8FG", "40y", "559", "6NS", "5kQ", "345", "cl",
|
||||
"1k2", "0nr", "boo", "74V", "6mO", "4Xl", "0cB", "2Va", "2xM", "0Mn", "42H", "6Cc", "4hB", "aws", "4V", "0Sl", "09H", "2Hc", "67E", "4Fn",
|
||||
"b5e", "aTo", "SB", "04x", "8WD", "9f", "6PQ", "4er", "4js", "5o3", "6g", "8XE", "1oP", "1z1", "65t", "704", "4Io", "68D", "Qs", "06I",
|
||||
"1LL", "2iN", "7BA", "4gC", "41g", "7Pm", "ms", "0NA", "14D", "2UN", "aDr", "5Kb", "4Vs", "5S3", "No", "19t", "0Cq", "1F1", "agn", "bBl",
|
||||
"43V", "aho", "oB", "0Lp", "16u", "An", "6lQ", "4Yr", "4TB", "6aa", "2ZO", "0ol", "1Qa", "br", "6OM", "4zn", "6QO", "4dl", "1Oc", "8x",
|
||||
"2DM", "05f", "5Za", "7oB", "5B0", "4Gp", "08V", "d7F", "5H", "0Rr", "bSo", "485", "474", "52t", "1MR", "1X3", "Pm", "07W", "4Hq", "5M1",
|
||||
"64j", "4EA", "1nN", "2KL", "7y", "0PC", "4km", "7No", "Ou", "0lG", "4Wi", "6bJ", "6Lf", "4yE", "00", "aY", "li", "8FC", "4tu", "5q5",
|
||||
"4O8", "4ZY", "0aw", "BE", "MD", "0nv", "4UX", "74R", "6NW", "5kU", "341", "ch", "nX", "0Mj", "42L", "6Cg", "6mK", "4Xh", "0cF", "2Ve",
|
||||
"09L", "2Hg", "67A", "4Fj", "4hF", "7MD", "4R", "0Sh", "1Ny", "9b", "6PU", "4ev", "4KZ", "7nX", "SF", "0pt", "1oT", "1z5", "65p", "5Tz",
|
||||
"4jw", "5o7", "6c", "0QY", "1LH", "2iJ", "6Rd", "4gG", "4Ik", "7li", "Qw", "06M", "7F", "246", "4kR", "7NP", "64U", "col", "1nq", "0k1",
|
||||
"PR", "E3", "4HN", "69e", "6SA", "4fb", "1Mm", "2ho", "5w", "0RM", "4ic", "7La", "66d", "4GO", "J2", "2IB", "Rc", "05Y", "b4D", "aUN",
|
||||
"4q2", "4dS", "8Ve", "8G", "8Hg", "bM", "4o0", "4zQ", "607", "75w", "La", "0oS", "T0", "AQ", "6ln", "4YM", "43i", "6BB", "2yl", "0LO",
|
||||
"0CN", "22e", "6MC", "5hA", "4VL", "6co", "NP", "0mb", "1ps", "0u3", "aDM", "baO", "41X", "7PR", "mL", "8Gf", "4IT", "7lV", "QH", "06r",
|
||||
"1Lw", "0I7", "5b8", "4gx", "4jH", "7OJ", "rT", "g5", "1ok", "2Ji", "65O", "4Dd", "4Ke", "7ng", "Sy", "04C", "h4", "2kD", "6Pj", "4eI",
|
||||
"4hy", "5m9", "4m", "0SW", "09s", "2HX", "4S4", "4FU", "4M6", "4XW", "0cy", "1f9", "ng", "0MU", "42s", "573", "6Nh", "5kj", "v6", "cW",
|
||||
"3KK", "0nI", "4Ug", "74m", "6oE", "4Zf", "0aH", "Bz", "lV", "y7", "40B", "6Ai", "582", "4yz", "0BT", "af", "OJ", "0lx", "4WV", "4B7",
|
||||
"64Q", "4Ez", "1nu", "0k5", "7B", "0Px", "4kV", "7NT", "6SE", "4ff", "1Mi", "2hk", "PV", "E7", "4HJ", "69a", "6rh", "4GK", "J6", "2IF",
|
||||
"5s", "0RI", "4ig", "7Le", "4q6", "4dW", "1OX", "8C", "Rg", "0qU", "5ZZ", "7oy", "4Ty", "5Q9", "Le", "0oW", "1QZ", "bI", "4o4", "4zU",
|
||||
"43m", "6BF", "oy", "0LK", "T4", "AU", "6lj", "4YI", "4VH", "6ck", "NT", "0mf", "0CJ", "22a", "6MG", "4xd", "4uT", "7PV", "mH", "0Nz",
|
||||
"1pw", "Cd", "aDI", "5KY", "1Ls", "0I3", "axM", "53U", "4IP", "7lR", "QL", "06v", "1oo", "2Jm", "65K", "5TA", "4jL", "7ON", "6X", "g1",
|
||||
"h0", "9Y", "6Pn", "4eM", "4Ka", "7nc", "2El", "04G", "09w", "d6g", "4S0", "4FQ", "bRN", "a3D", "4i", "0SS", "nc", "0MQ", "42w", "577",
|
||||
"4M2", "4XS", "17T", "dlm", "3KO", "0nM", "4Uc", "74i", "6Nl", "5kn", "v2", "cS", "lR", "y3", "40F", "6Am", "6oA", "4Zb", "0aL", "2To",
|
||||
"ON", "18U", "4WR", "4B3", "586", "bCM", "0BP", "ab", "PZ", "0sh", "4HF", "69m", "6SI", "4fj", "1Me", "2hg", "7N", "0Pt", "4kZ", "7NX",
|
||||
"6pU", "4Ev", "1ny", "0k9", "Rk", "05Q", "4Jw", "5O7", "452", "50r", "1OT", "8O", "qw", "0RE", "4ik", "7Li", "66l", "4GG", "08a", "2IJ",
|
||||
"T8", "AY", "6lf", "4YE", "43a", "6BJ", "ou", "0LG", "0Aw", "bE", "4o8", "4zY", "4Tu", "5Q5", "Li", "8fC", "14s", "Ch", "6nW", "5KU",
|
||||
"41P", "7PZ", "mD", "0Nv", "0CF", "22m", "6MK", "4xh", "4VD", "6cg", "NX", "0mj", "5za", "7OB", "6T", "0Qn", "1oc", "2Ja", "65G", "4Dl",
|
||||
"b7g", "68w", "1w2", "06z", "8UF", "dSN", "5b0", "4gp", "4hq", "5m1", "4e", "8ZG", "1mR", "1x3", "67v", "726", "4Km", "7no", "Sq", "04K",
|
||||
"1NN", "9U", "6Pb", "4eA", "adr", "5kb", "26", "21F", "Ms", "0nA", "4Uo", "74e", "79U", "bbl", "0cq", "1f1", "no", "396", "4vs", "5s3",
|
||||
"6LQ", "4yr", "367", "an", "OB", "0lp", "bmm", "76T", "6oM", "4Zn", "15i", "Br", "2zO", "0Ol", "40J", "6Aa", "6SM", "4fn", "1Ma", "2hc",
|
||||
"2FO", "07d", "4HB", "69i", "64Y", "4Er", "83L", "d5D", "7J", "0Pp", "bQm", "a0g", "456", "50v", "1OP", "8K", "Ro", "05U", "4Js", "5O3",
|
||||
"66h", "4GC", "08e", "2IN", "qs", "0RA", "4io", "7Lm", "43e", "6BN", "oq", "0LC", "0bo", "2WL", "6lb", "4YA", "4Tq", "5Q1", "Lm", "8fG",
|
||||
"0As", "bA", "ael", "cPO", "41T", "ajm", "1K2", "0Nr", "14w", "Cl", "6nS", "5KQ", "5Fa", "6cc", "2XM", "0mn", "0CB", "22i", "6MO", "4xl",
|
||||
"1og", "2Je", "65C", "4Dh", "4jD", "7OF", "6P", "g9", "3l9", "2iy", "5b4", "4gt", "4IX", "68s", "QD", "0rv", "1mV", "1x7", "4S8", "4FY",
|
||||
"4hu", "5m5", "4a", "1Cz", "h8", "9Q", "6Pf", "4eE", "4Ki", "7nk", "Su", "04O", "Mw", "0nE", "4Uk", "74a", "6Nd", "5kf", "22", "21B",
|
||||
"nk", "0MY", "4vw", "5s7", "6mx", "5Hz", "0cu", "1f5", "OF", "0lt", "4WZ", "6by", "6LU", "4yv", "0BX", "aj", "lZ", "0Oh", "40N", "6Ae",
|
||||
"6oI", "4Zj", "0aD", "Bv", "5f", "9Ke", "4ir", "5l2", "66u", "735", "08x", "1y0", "Rr", "05H", "4Jn", "7ol", "6Qa", "4dB", "1OM", "8V",
|
||||
"7W", "0Pm", "4kC", "7NA", "64D", "4Eo", "83Q", "2Kb", "PC", "07y", "b6d", "69t", "5c3", "4fs", "8TE", "dRM", "374", "22t", "599", "4xq",
|
||||
"bln", "77W", "NA", "0ms", "14j", "Cq", "6nN", "5KL", "41I", "7PC", "3km", "0No", "35", "20E", "6Oc", "5ja", "4Tl", "6aO", "Lp", "0oB",
|
||||
"0br", "1g2", "78V", "bco", "43x", "568", "ol", "385", "4Kt", "5N4", "Sh", "04R", "1NW", "9L", "441", "4eX", "4hh", "7Mj", "pt", "0SF",
|
||||
"K9", "2HI", "67o", "4FD", "4IE", "68n", "QY", "0", "1Lf", "2id", "6RJ", "4gi", "4jY", "auh", "6M", "0Qw", "1oz", "2Jx", "5A5", "4Du",
|
||||
"6oT", "4Zw", "0aY", "Bk", "lG", "0Ou", "40S", "6Ax", "6LH", "4yk", "0BE", "aw", "2YJ", "0li", "4WG", "6bd", "6me", "4XF", "0ch", "2VK",
|
||||
"nv", "0MD", "42b", "6CI", "6Ny", "7K9", "1PU", "cF", "Mj", "0nX", "4Uv", "5P6", "66q", "4GZ", "1lU", "1y4", "5b", "0RX", "4iv", "5l6",
|
||||
"6Qe", "4dF", "1OI", "8R", "Rv", "05L", "4Jj", "7oh", "6pH", "4Ek", "1nd", "2Kf", "7S", "0Pi", "4kG", "7NE", "5c7", "4fw", "1Mx", "0H8",
|
||||
"PG", "0su", "5Xz", "69p", "4VY", "4C8", "NE", "0mw", "1Sz", "22p", "6MV", "4xu", "41M", "7PG", "mY", "x8", "14n", "Cu", "6nJ", "5KH",
|
||||
"4Th", "6aK", "Lt", "0oF", "31", "bX", "6Og", "4zD", "4wt", "5r4", "oh", "0LZ", "0bv", "AD", "4L9", "4YX", "1NS", "9H", "445", "51u",
|
||||
"4Kp", "5N0", "Sl", "04V", "09f", "2HM", "67k", "5Va", "4hl", "7Mn", "4x", "0SB", "1Lb", "3yA", "6RN", "4gm", "4IA", "68j", "2GL", "4",
|
||||
"82O", "d4G", "5A1", "4Dq", "bPn", "a1d", "6I", "0Qs", "lC", "0Oq", "40W", "akn", "6oP", "4Zs", "15t", "Bo", "2YN", "0lm", "4WC", "76I",
|
||||
"6LL", "4yo", "0BA", "as", "nr", "8DX", "42f", "6CM", "6ma", "4XB", "0cl", "2VO", "Mn", "8gD", "4Ur", "5P2", "ado", "bAm", "1PQ", "cB",
|
||||
"Rz", "0qH", "4Jf", "7od", "6Qi", "4dJ", "i7", "2jG", "5n", "0RT", "4iz", "7Lx", "4R7", "4GV", "08p", "1y8", "PK", "07q", "4HW", "7mU",
|
||||
"6SX", "52R", "1Mt", "0H4", "sW", "f6", "4kK", "7NI", "64L", "4Eg", "1nh", "2Kj", "14b", "Cy", "6nF", "5KD", "41A", "7PK", "mU", "x4",
|
||||
"0CW", "0V6", "591", "4xy", "4VU", "4C4", "NI", "19R", "0bz", "AH", "4L5", "4YT", "43p", "560", "od", "0LV", "w5", "bT", "6Ok", "4zH",
|
||||
"4Td", "6aG", "Lx", "0oJ", "5xA", "7Mb", "4t", "0SN", "K1", "2HA", "67g", "4FL", "b5G", "aTM", "0e3", "04Z", "8Wf", "9D", "449", "4eP",
|
||||
"4jQ", "7OS", "6E", "255", "1or", "0j2", "65V", "cno", "4IM", "68f", "QQ", "8", "1Ln", "2il", "6RB", "4ga", "afR", "4yc", "0BM", "23f",
|
||||
"OS", "Z2", "4WO", "6bl", "aEN", "c4e", "0aQ", "Bc", "lO", "8Fe", "4tS", "4a2", "4n3", "5ks", "8Id", "cN", "Mb", "0nP", "614", "74t",
|
||||
"6mm", "4XN", "U3", "2VC", "2xo", "0ML", "42j", "6CA", "6Qm", "4dN", "i3", "8Z", "2Do", "05D", "4Jb", "aUS", "4R3", "4GR", "08t", "d7d",
|
||||
"5j", "0RP", "bSM", "a2G", "ayN", "52V", "1Mp", "0H0", "PO", "07u", "4HS", "69x", "64H", "4Ec", "1nl", "2Kn", "sS", "f2", "4kO", "7NM",
|
||||
"41E", "7PO", "mQ", "x0", "14f", "2Ul", "6nB", "aQ1", "4VQ", "4C0", "NM", "19V", "0CS", "0V2", "595", "bBN", "43t", "564", "0Y3", "0LR",
|
||||
"16W", "AL", "4L1", "4YP", "5DA", "6aC", "2Zm", "0oN", "39", "bP", "6Oo", "4zL", "K5", "2HE", "67c", "4FH", "4hd", "7Mf", "4p", "0SJ",
|
||||
"8Wb", "2kY", "4p5", "4eT", "4Kx", "5N8", "Sd", "0pV", "1ov", "0j6", "5A9", "4Dy", "4jU", "7OW", "6A", "1AZ", "1Lj", "2ih", "6RF", "4ge",
|
||||
"4II", "68b", "QU", "D4", "OW", "Z6", "4WK", "6bh", "6LD", "4yg", "0BI", "23b", "lK", "0Oy", "4tW", "4a6", "6oX", "5JZ", "0aU", "Bg",
|
||||
"Mf", "0nT", "4Uz", "74p", "4n7", "5kw", "1PY", "cJ", "nz", "0MH", "42n", "6CE", "6mi", "4XJ", "U7", "2VG", "4MP", "4X1", "UL", "02v",
|
||||
"0XR", "0M3", "a8E", "57U", "4nL", "7KN", "2X", "c1", "1ko", "2Nm", "61K", "5PA", "4Oa", "6zB", "2Al", "00G", "l0", "yQ", "6Tn", "4aM",
|
||||
"58T", "a7D", "0i", "0WS", "84o", "ZM", "4W0", "4BQ", "4I2", "5Lr", "13T", "G", "jc", "0IQ", "46w", "537", "6Jl", "5on", "r2", "gS",
|
||||
"3OO", "0jM", "4Qc", "70i", "6kA", "5NC", "0eL", "2Po", "hR", "8Bx", "44F", "6Em", "abO", "49v", "0FP", "eb", "KN", "8ad", "4SR", "4F3",
|
||||
"3B", "0Tx", "4oV", "4z7", "60Q", "4Az", "0zT", "Yf", "TV", "A7", "4LJ", "6yi", "6WE", "4bf", "0YH", "zz", "1s", "0VI", "4mg", "6XD",
|
||||
"6vh", "4CK", "N6", "2MF", "Vg", "0uU", "6n9", "7ky", "4u6", "5pv", "1KX", "xK", "1UZ", "fI", "4k4", "5nt", "4Py", "5U9", "He", "0kW",
|
||||
"P4", "EU", "6hj", "5Mh", "47m", "6FF", "ky", "0HK", "0GJ", "dx", "6IG", "48l", "4RH", "6gk", "JT", "0if", "0dV", "Gd", "5Z8", "5OY",
|
||||
"4qT", "4d5", "iH", "0Jz", "0XV", "0M7", "5f8", "4cx", "4MT", "4X5", "UH", "02r", "1kk", "Xx", "61O", "5PE", "4nH", "7KJ", "vT", "c5",
|
||||
"l4", "yU", "6Tj", "4aI", "4Oe", "6zF", "Wy", "00C", "1iZ", "ZI", "4W4", "4BU", "4ly", "5i9", "0m", "0WW", "jg", "0IU", "46s", "533",
|
||||
"4I6", "5Lv", "0gy", "C", "3OK", "0jI", "4Qg", "6dD", "6Jh", "5oj", "r6", "gW", "hV", "0Kd", "44B", "6Ei", "6kE", "5NG", "0eH", "Fz",
|
||||
"KJ", "0hx", "4SV", "4F7", "6HY", "49r", "0FT", "ef", "60U", "ckl", "0zP", "Yb", "3F", "206", "4oR", "4z3", "6WA", "4bb", "0YL", "2lo",
|
||||
"TR", "A3", "4LN", "6ym", "62d", "4CO", "N2", "2MB", "1w", "0VM", "4mc", "7Ha", "4u2", "54z", "8Re", "xO", "Vc", "01Y", "b0D", "aQN",
|
||||
"647", "71w", "Ha", "0kS", "8Lg", "fM", "4k0", "5np", "47i", "6FB", "29d", "0HO", "P0", "EQ", "6hn", "5Ml", "4RL", "6go", "JP", "0ib",
|
||||
"0GN", "26e", "6IC", "48h", "45X", "4d1", "iL", "8Cf", "0dR", "0q3", "hYt", "beO", "4nD", "7KF", "2P", "c9", "1kg", "Xt", "61C", "5PI",
|
||||
"4MX", "4X9", "UD", "0vv", "0XZ", "2my", "5f4", "4ct", "4lu", "5i5", "0a", "1Gz", "0yw", "ZE", "4W8", "4BY", "4Oi", "6zJ", "Wu", "00O",
|
||||
"l8", "yY", "6Tf", "4aE", "6Jd", "5of", "62", "25B", "Iw", "0jE", "4Qk", "6dH", "6ix", "5Lz", "0gu", "O", "jk", "0IY", "4rw", "5w7",
|
||||
"5x6", "5mW", "0FX", "ej", "KF", "0ht", "4SZ", "6fy", "6kI", "5NK", "0eD", "Fv", "hZ", "93", "44N", "6Ee", "2BO", "03d", "4LB", "6ya",
|
||||
"6WM", "4bn", "1Ia", "zr", "3J", "0Tp", "bUm", "a4g", "5D2", "4Ar", "87L", "Yn", "Vo", "01U", "4Ns", "5K3", "416", "54v", "1KP", "xC",
|
||||
"us", "0VA", "4mo", "6XL", "62h", "4CC", "0xm", "2MN", "0fo", "2SL", "6hb", "bgr", "47e", "6FN", "kq", "0HC", "0Es", "fA", "aal", "bDn",
|
||||
"4Pq", "5U1", "Hm", "8bG", "10w", "Gl", "5Z0", "5OQ", "45T", "anm", "1O2", "0Jr", "0GB", "dp", "6IO", "48d", "5Ba", "6gc", "3Ll", "0in",
|
||||
"1kc", "Xp", "61G", "5PM", "bTs", "7KB", "2T", "0Un", "8QF", "39T", "5f0", "4cp", "797", "aRm", "1s2", "02z", "0ys", "ZA", "63v", "766",
|
||||
"4lq", "5i1", "0e", "9Nf", "0Zo", "2oL", "6Tb", "4aA", "4Om", "6zN", "Wq", "00K", "Is", "0jA", "4Qo", "6dL", "7ZA", "5ob", "66", "25F",
|
||||
"jo", "9Pd", "4rs", "5w3", "aCn", "bfl", "0gq", "K", "KB", "0hp", "bim", "72T", "5x2", "49z", "327", "en", "3nn", "97", "44J", "6Ea",
|
||||
"6kM", "5NO", "11i", "Fr", "6WI", "4bj", "0YD", "zv", "TZ", "0wh", "4LF", "6ye", "5D6", "4Av", "0zX", "Yj", "3N", "0Tt", "4oZ", "6Zy",
|
||||
"412", "54r", "1KT", "xG", "Vk", "01Q", "4Nw", "5K7", "62l", "4CG", "0xi", "2MJ", "uw", "0VE", "4mk", "6XH", "47a", "6FJ", "ku", "0HG",
|
||||
"P8", "EY", "6hf", "5Md", "4Pu", "5U5", "Hi", "8bC", "0Ew", "fE", "4k8", "5nx", "45P", "4d9", "iD", "0Jv", "0dZ", "Gh", "5Z4", "5OU",
|
||||
"4RD", "6gg", "JX", "0ij", "0GF", "dt", "6IK", "5lI", "4Op", "5J0", "Wl", "00V", "0Zr", "2oQ", "405", "55u", "4ll", "6YO", "0x", "0WB",
|
||||
"0yn", "2LM", "63k", "5Ra", "4MA", "6xb", "2CL", "02g", "0XC", "39I", "6VN", "4cm", "bTn", "a5d", "2I", "0Us", "86O", "Xm", "5E1", "5PP",
|
||||
"6kP", "5NR", "11t", "Fo", "hC", "0Kq", "44W", "aon", "6HL", "49g", "0FA", "es", "3Mo", "0hm", "4SC", "72I", "6ia", "5Lc", "0gl", "V",
|
||||
"jr", "1Ya", "46f", "6GM", "hyV", "bEm", "0Dp", "gB", "In", "8cD", "4Qr", "5T2", "1b", "0VX", "4mv", "5h6", "62q", "4CZ", "0xt", "2MW",
|
||||
"Vv", "01L", "4Nj", "7kh", "6Ue", "54o", "1KI", "xZ", "3S", "0Ti", "4oG", "6Zd", "6tH", "4Ak", "0zE", "Yw", "TG", "0wu", "780", "6yx",
|
||||
"5g7", "4bw", "0YY", "zk", "1Wz", "di", "5y5", "5lT", "4RY", "4G8", "JE", "0iw", "0dG", "Gu", "6jJ", "5OH", "45M", "6Df", "iY", "80",
|
||||
"71", "fX", "6Kg", "5ne", "4Ph", "6eK", "Ht", "0kF", "0fv", "ED", "4H9", "5My", "4st", "5v4", "kh", "0HZ", "0Zv", "yD", "401", "4aX",
|
||||
"4Ot", "5J4", "Wh", "00R", "O9", "ZX", "63o", "4BD", "4lh", "6YK", "tt", "0WF", "0XG", "2md", "6VJ", "4ci", "4ME", "6xf", "UY", "02c",
|
||||
"1kz", "Xi", "5E5", "5PT", "4nY", "aqh", "2M", "0Uw", "hG", "0Ku", "44S", "6Ex", "6kT", "5NV", "0eY", "Fk", "3Mk", "0hi", "4SG", "6fd",
|
||||
"6HH", "49c", "0FE", "ew", "jv", "0ID", "46b", "6GI", "6ie", "5Lg", "0gh", "R", "Ij", "0jX", "4Qv", "5T6", "6Jy", "7O9", "0Dt", "gF",
|
||||
"62u", "775", "0xp", "198", "1f", "9Oe", "4mr", "5h2", "6Ua", "54k", "1KM", "2nO", "Vr", "01H", "4Nn", "7kl", "60D", "4Ao", "0zA", "Ys",
|
||||
"3W", "0Tm", "4oC", "7JA", "5g3", "4bs", "8PE", "zo", "TC", "03y", "784", "aSn", "bhn", "73W", "JA", "0is", "334", "dm", "5y1", "48y",
|
||||
"45I", "6Db", "3om", "84", "0dC", "Gq", "6jN", "5OL", "4Pl", "6eO", "Hp", "0kB", "75", "24E", "6Kc", "5na", "47x", "528", "kl", "8AF",
|
||||
"0fr", "1c2", "aBm", "bgo", "4ld", "6YG", "0p", "0WJ", "O5", "ZT", "63c", "4BH", "4Ox", "5J8", "Wd", "0tV", "0Zz", "yH", "4t5", "4aT",
|
||||
"4nU", "7KW", "2A", "1EZ", "1kv", "Xe", "5E9", "5PX", "4MI", "6xj", "UU", "02o", "0XK", "2mh", "6VF", "4ce", "6HD", "49o", "0FI", "27b",
|
||||
"KW", "0he", "4SK", "6fh", "6kX", "5NZ", "0eU", "Fg", "hK", "0Ky", "4pW", "4e6", "4j7", "5ow", "0Dx", "gJ", "If", "0jT", "4Qz", "6dY",
|
||||
"6ii", "5Lk", "Q7", "DV", "jz", "0IH", "46n", "6GE", "3PN", "01D", "4Nb", "aQS", "6Um", "54g", "m3", "xR", "1j", "0VP", "59W", "a6G",
|
||||
"4V3", "4CR", "85l", "194", "TO", "03u", "4LS", "4Y2", "a9F", "56V", "0YQ", "zc", "wS", "b2", "4oO", "6Zl", "60H", "4Ac", "0zM", "2On",
|
||||
"0dO", "2Ql", "6jB", "aU1", "45E", "6Dn", "iQ", "88", "0GS", "da", "acL", "48u", "4RQ", "4G0", "JM", "94N", "12W", "EL", "4H1", "5Mq",
|
||||
"47t", "524", "29y", "0HR", "79", "fP", "6Ko", "5nm", "aZ0", "6eC", "3NL", "0kN", "O1", "ZP", "63g", "4BL", "58I", "6YC", "0t", "0WN",
|
||||
"8Sf", "yL", "409", "4aP", "b1G", "aPM", "0a3", "00Z", "1kr", "Xa", "61V", "bzN", "4nQ", "7KS", "2E", "215", "0XO", "2ml", "6VB", "4ca",
|
||||
"4MM", "6xn", "UQ", "02k", "KS", "0ha", "4SO", "6fl", "7Xa", "49k", "0FM", "27f", "hO", "8Be", "4pS", "4e2", "aAN", "bdL", "0eQ", "Fc",
|
||||
"Ib", "0jP", "654", "70t", "4j3", "5os", "8Md", "gN", "28g", "0IL", "46j", "6GA", "6im", "5Lo", "Q3", "Z", "6Ui", "54c", "m7", "xV",
|
||||
"Vz", "0uH", "4Nf", "7kd", "4V7", "4CV", "0xx", "190", "1n", "0VT", "4mz", "6XY", "6WX", "56R", "0YU", "zg", "TK", "03q", "4LW", "4Y6",
|
||||
"60L", "4Ag", "0zI", "2Oj", "wW", "b6", "4oK", "6Zh", "45A", "6Dj", "iU", "0Jg", "0dK", "Gy", "6jF", "5OD", "4RU", "4G4", "JI", "1yZ",
|
||||
"0GW", "de", "5y9", "48q", "47p", "520", "kd", "0HV", "0fz", "EH", "4H5", "5Mu", "4Pd", "6eG", "Hx", "0kJ", "s5", "fT", "6Kk", "5ni",
|
||||
"5Y1", "5LP", "13v", "e", "jA", "0Is", "46U", "aml", "6JN", "5oL", "0DC", "gq", "3Om", "0jo", "4QA", "6db", "6kc", "5Na", "0en", "2PM",
|
||||
"hp", "0KB", "44d", "6EO", "abm", "49T", "0Fr", "1C2", "Kl", "8aF", "4Sp", "5V0", "4Mr", "5H2", "Un", "02T", "0Xp", "2mS", "427", "57w",
|
||||
"4nn", "7Kl", "2z", "1Ea", "1kM", "2NO", "61i", "5Pc", "4OC", "7jA", "2AN", "00e", "0ZA", "ys", "6TL", "4ao", "58v", "a7f", "0K", "0Wq",
|
||||
"84M", "Zo", "5G3", "4Bs", "0EY", "fk", "6KT", "5nV", "bjh", "6ex", "HG", "0ku", "0fE", "Ew", "6hH", "5MJ", "47O", "6Fd", "29B", "0Hi",
|
||||
"53", "dZ", "6Ie", "48N", "4Rj", "6gI", "Jv", "0iD", "0dt", "GF", "6jy", "7o9", "4qv", "5t6", "ij", "0JX", "wh", "0TZ", "4ot", "5j4",
|
||||
"4T9", "4AX", "0zv", "YD", "Tt", "03N", "4Lh", "6yK", "6Wg", "4bD", "o9", "zX", "1Q", "0Vk", "4mE", "6Xf", "62B", "4Ci", "0xG", "2Md",
|
||||
"VE", "0uw", "4NY", "aQh", "5e5", "5pT", "1Kz", "xi", "jE", "0Iw", "46Q", "4g8", "5Y5", "5LT", "13r", "a", "IY", "0jk", "4QE", "6df",
|
||||
"6JJ", "5oH", "0DG", "gu", "ht", "0KF", "4ph", "6EK", "6kg", "5Ne", "S9", "FX", "Kh", "0hZ", "4St", "5V4", "4h9", "49P", "0Fv", "eD",
|
||||
"0Xt", "2mW", "423", "4cZ", "4Mv", "5H6", "Uj", "02P", "1kI", "XZ", "61m", "5Pg", "4nj", "7Kh", "vv", "0UD", "0ZE", "yw", "6TH", "4ak",
|
||||
"4OG", "6zd", "2AJ", "00a", "0yY", "Zk", "5G7", "4Bw", "58r", "6Yx", "0O", "0Wu", "bjl", "71U", "HC", "0kq", "316", "fo", "6KP", "5nR",
|
||||
"47K", "7VA", "29F", "0Hm", "0fA", "Es", "6hL", "5MN", "4Rn", "6gM", "Jr", "1ya", "57", "26G", "6Ia", "48J", "45z", "5t2", "in", "8CD",
|
||||
"0dp", "GB", "hYV", "bem", "60w", "757", "0zr", "2OQ", "3d", "9Mg", "4op", "5j0", "6Wc", "56i", "0Yn", "2lM", "Tp", "03J", "4Ll", "6yO",
|
||||
"62F", "4Cm", "0xC", "dwS", "1U", "0Vo", "4mA", "6Xb", "5e1", "54X", "8RG", "xm", "VA", "0us", "b0f", "aQl", "6JF", "5oD", "0DK", "gy",
|
||||
"IU", "0jg", "4QI", "6dj", "5Y9", "5LX", "0gW", "m", "jI", "1YZ", "4rU", "4g4", "4h5", "5mu", "0Fz", "eH", "Kd", "0hV", "4Sx", "5V8",
|
||||
"6kk", "5Ni", "S5", "FT", "hx", "0KJ", "44l", "6EG", "4nf", "7Kd", "2r", "0UH", "M7", "XV", "61a", "5Pk", "4Mz", "6xY", "Uf", "0vT",
|
||||
"0Xx", "39r", "4v7", "4cV", "4lW", "4y6", "0C", "0Wy", "0yU", "Zg", "63P", "5RZ", "4OK", "6zh", "WW", "B6", "0ZI", "2oj", "6TD", "4ag",
|
||||
"0fM", "2Sn", "7xa", "5MB", "47G", "6Fl", "kS", "0Ha", "0EQ", "fc", "aaN", "bDL", "4PS", "4E2", "HO", "8be", "10U", "GN", "4J3", "5Os",
|
||||
"45v", "506", "ib", "0JP", "q3", "dR", "6Im", "48F", "4Rb", "6gA", "3LN", "0iL", "2Bm", "03F", "aF0", "6yC", "6Wo", "4bL", "o1", "zP",
|
||||
"3h", "0TR", "bUO", "a4E", "4T1", "4AP", "87n", "YL", "VM", "01w", "4NQ", "7kS", "hfu", "54T", "1Kr", "xa", "1Y", "0Vc", "4mM", "6Xn",
|
||||
"62J", "4Ca", "0xO", "2Ml", "IQ", "0jc", "4QM", "6dn", "6JB", "a19", "0DO", "25d", "jM", "9PF", "46Y", "4g0", "aCL", "687", "0gS", "i",
|
||||
"3MP", "0hR", "676", "72v", "4h1", "49X", "8Of", "eL", "3nL", "0KN", "44h", "6EC", "6ko", "5Nm", "S1", "FP", "M3", "XR", "61e", "5Po",
|
||||
"4nb", "aqS", "2v", "0UL", "8Qd", "39v", "4v3", "4cR", "b3E", "aRO", "Ub", "02X", "0yQ", "Zc", "63T", "bxL", "4lS", "4y2", "0G", "237",
|
||||
"0ZM", "2on", "7Da", "4ac", "4OO", "6zl", "WS", "B2", "47C", "6Fh", "kW", "0He", "0fI", "2Sj", "6hD", "5MF", "4PW", "4E6", "HK", "0ky",
|
||||
"0EU", "fg", "6KX", "5nZ", "45r", "502", "if", "0JT", "0dx", "GJ", "4J7", "5Ow", "4Rf", "6gE", "Jz", "0iH", "q7", "dV", "6Ii", "48B",
|
||||
"6Wk", "4bH", "o5", "zT", "Tx", "03B", "4Ld", "6yG", "4T5", "4AT", "0zz", "YH", "3l", "0TV", "4ox", "5j8", "5e9", "54P", "1Kv", "xe",
|
||||
"VI", "01s", "4NU", "7kW", "62N", "4Ce", "0xK", "2Mh", "uU", "0Vg", "4mI", "6Xj", "4K0", "5Np", "11V", "FM", "ha", "0KS", "44u", "515",
|
||||
"6Hn", "49E", "48", "eQ", "3MM", "0hO", "4Sa", "6fB", "6iC", "5LA", "0gN", "t", "jP", "0Ib", "46D", "6Go", "hyt", "bEO", "0DR", "0Q3",
|
||||
"IL", "8cf", "4QP", "4D1", "4OR", "4Z3", "WN", "00t", "0ZP", "yb", "hgv", "55W", "4lN", "6Ym", "0Z", "a3", "0yL", "2Lo", "63I", "4Bb",
|
||||
"4Mc", "7ha", "2Cn", "02E", "n2", "2mB", "6Vl", "4cO", "bTL", "a5F", "2k", "0UQ", "86m", "XO", "4U2", "5Pr", "0Gy", "dK", "4i6", "5lv",
|
||||
"5BZ", "6gX", "Jg", "0iU", "R6", "GW", "6jh", "5Oj", "45o", "6DD", "3oK", "0JI", "0EH", "fz", "6KE", "5nG", "4PJ", "6ei", "HV", "0kd",
|
||||
"0fT", "Ef", "6hY", "690", "4sV", "4f7", "kJ", "0Hx", "uH", "0Vz", "4mT", "4x5", "5F8", "4Cx", "0xV", "0m7", "VT", "C5", "4NH", "7kJ",
|
||||
"6UG", "54M", "1Kk", "xx", "3q", "0TK", "4oe", "6ZF", "60b", "4AI", "L4", "YU", "Te", "0wW", "4Ly", "5I9", "4w4", "4bU", "1IZ", "zI",
|
||||
"he", "0KW", "44q", "511", "4K4", "5Nt", "11R", "FI", "Ky", "0hK", "4Se", "6fF", "6Hj", "49A", "p4", "eU", "jT", "0If", "4rH", "6Gk",
|
||||
"6iG", "5LE", "0gJ", "p", "IH", "0jz", "4QT", "4D5", "5z8", "5oY", "0DV", "gd", "0ZT", "yf", "6TY", "4az", "4OV", "4Z7", "WJ", "00p",
|
||||
"0yH", "Zz", "63M", "4Bf", "4lJ", "6Yi", "tV", "a7", "n6", "2mF", "6Vh", "4cK", "4Mg", "6xD", "2Cj", "02A", "1kX", "XK", "4U6", "5Pv",
|
||||
"6N9", "7Ky", "2o", "0UU", "665", "73u", "Jc", "0iQ", "8Ne", "dO", "4i2", "5lr", "45k", "7Ta", "3oO", "0JM", "R2", "GS", "6jl", "5On",
|
||||
"4PN", "6em", "HR", "8bx", "0EL", "24g", "6KA", "5nC", "47Z", "4f3", "kN", "8Ad", "0fP", "Eb", "aBO", "694", "62W", "byO", "0xR", "0m3",
|
||||
"1D", "224", "4mP", "4x1", "6UC", "54I", "1Ko", "2nm", "VP", "C1", "4NL", "7kN", "60f", "4AM", "L0", "YQ", "3u", "0TO", "4oa", "6ZB",
|
||||
"438", "4bQ", "8Pg", "zM", "Ta", "0wS", "b2F", "aSL", "6Hf", "49M", "40", "eY", "Ku", "0hG", "4Si", "6fJ", "4K8", "5Nx", "0ew", "FE",
|
||||
"hi", "8BC", "4pu", "5u5", "5z4", "5oU", "0DZ", "gh", "ID", "0jv", "4QX", "4D9", "6iK", "5LI", "0gF", "Dt", "jX", "0Ij", "46L", "6Gg",
|
||||
"4lF", "6Ye", "0R", "0Wh", "0yD", "Zv", "63A", "4Bj", "4OZ", "6zy", "WF", "0tt", "0ZX", "yj", "5d6", "4av", "4nw", "5k7", "2c", "0UY",
|
||||
"1kT", "XG", "61p", "5Pz", "4Mk", "6xH", "Uw", "02M", "0Xi", "2mJ", "6Vd", "4cG", "0dm", "2QN", "7zA", "5Ob", "45g", "6DL", "is", "0JA",
|
||||
"0Gq", "dC", "acn", "48W", "4Rs", "5W3", "Jo", "94l", "12u", "En", "5X2", "5MS", "47V", "alo", "kB", "0Hp", "1Ua", "fr", "6KM", "5nO",
|
||||
"4PB", "6ea", "3Nn", "0kl", "3Pl", "01f", "bts", "7kB", "6UO", "54E", "1Kc", "xp", "1H", "0Vr", "59u", "a6e", "5F0", "4Cp", "85N", "d3F",
|
||||
"Tm", "03W", "4Lq", "5I1", "434", "56t", "0Ys", "zA", "3y", "0TC", "4om", "6ZN", "60j", "4AA", "0zo", "2OL", "Kq", "0hC", "4Sm", "6fN",
|
||||
"6Hb", "49I", "44", "27D", "hm", "8BG", "44y", "519", "aAl", "bdn", "0es", "FA", "1o2", "0jr", "bko", "70V", "5z0", "5oQ", "305", "gl",
|
||||
"28E", "0In", "46H", "6Gc", "6iO", "5LM", "0gB", "x", "1ia", "Zr", "63E", "4Bn", "4lB", "6Ya", "0V", "0Wl", "8SD", "yn", "5d2", "4ar",
|
||||
"b1e", "aPo", "WB", "00x", "1kP", "XC", "61t", "744", "4ns", "5k3", "2g", "9Ld", "0Xm", "2mN", "7FA", "4cC", "4Mo", "6xL", "Us", "02I",
|
||||
"45c", "6DH", "iw", "0JE", "0di", "2QJ", "6jd", "5Of", "4Rw", "5W7", "Jk", "0iY", "0Gu", "dG", "6Ix", "48S", "47R", "6Fy", "kF", "0Ht",
|
||||
"0fX", "Ej", "5X6", "5MW", "4PF", "6ee", "HZ", "0kh", "0ED", "fv", "6KI", "5nK", "6UK", "54A", "1Kg", "xt", "VX", "C9", "4ND", "7kF",
|
||||
"5F4", "4Ct", "0xZ", "2My", "1L", "0Vv", "4mX", "4x9", "430", "4bY", "0Yw", "zE", "Ti", "03S", "4Lu", "5I5", "60n", "4AE", "L8", "YY",
|
||||
"wu", "0TG", "4oi", "6ZJ" };
|
||||
|
||||
|
||||
#endif
|
||||
|
181
src/db.c
181
src/db.c
@ -38,6 +38,8 @@
|
||||
* C-level DB API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
int keyIsExpired(redisDb *db, robj *key);
|
||||
|
||||
/* Update LFU when an object is accessed.
|
||||
* Firstly, decrement the counter if the decrement time is reached.
|
||||
* Then logarithmically increment the counter, and update the access time. */
|
||||
@ -81,6 +83,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
* 1. A key gets expired if it reached it's TTL.
|
||||
* 2. The key last access time is updated.
|
||||
* 3. The global keys hits/misses stats are updated (reported in INFO).
|
||||
* 4. If keyspace notifications are enabled, a "keymiss" notification is fired.
|
||||
*
|
||||
* This API should not be used when we write to the key after obtaining
|
||||
* the object linked to the key, but only for read only operations.
|
||||
@ -90,7 +93,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
* LOOKUP_NONE (or zero): no special flags are passed.
|
||||
* LOOKUP_NOTOUCH: don't alter the last access time of the key.
|
||||
*
|
||||
* Note: this function also returns NULL is the key is logically expired
|
||||
* Note: this function also returns NULL if the key is logically expired
|
||||
* but still existing, in case this is a slave, since this API is called only
|
||||
* for read operations. Even if the key expiry is master-driven, we can
|
||||
* correctly report a key is expired on slaves even if the master is lagging
|
||||
@ -102,7 +105,11 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
/* Key expired. If we are in the context of a master, expireIfNeeded()
|
||||
* returns 0 only when the key does not exist at all, so it's safe
|
||||
* to return NULL ASAP. */
|
||||
if (server.masterhost == NULL) return NULL;
|
||||
if (server.masterhost == NULL) {
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* However if we are in the context of a slave, expireIfNeeded() will
|
||||
* not really try to expire the key, it only returns information
|
||||
@ -113,7 +120,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
* safety measure, the command invoked is a read-only command, we can
|
||||
* safely return NULL here, and provide a more consistent behavior
|
||||
* to clients accessign expired values in a read-only fashion, that
|
||||
* will say the key as non exisitng.
|
||||
* will say the key as non existing.
|
||||
*
|
||||
* Notably this covers GETs when slaves are used to scale reads. */
|
||||
if (server.current_client &&
|
||||
@ -121,12 +128,16 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
server.current_client->cmd &&
|
||||
server.current_client->cmd->flags & CMD_READONLY)
|
||||
{
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
val = lookupKey(db,key,flags);
|
||||
if (val == NULL)
|
||||
if (val == NULL) {
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
}
|
||||
else
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
@ -184,14 +195,19 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
serverAssertWithInfo(NULL,key,de != NULL);
|
||||
dictEntry auxentry = *de;
|
||||
robj *old = dictGetVal(de);
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
robj *old = dictGetVal(de);
|
||||
int saved_lru = old->lru;
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
val->lru = saved_lru;
|
||||
} else {
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
val->lru = old->lru;
|
||||
}
|
||||
dictSetVal(db->dict, de, val);
|
||||
|
||||
if (server.lazyfree_lazy_server_del) {
|
||||
freeObjAsync(old);
|
||||
dictSetVal(db->dict, &auxentry, NULL);
|
||||
}
|
||||
|
||||
dictFreeVal(db->dict, &auxentry);
|
||||
}
|
||||
|
||||
/* High level Set operation. This function can be used in order to set
|
||||
@ -201,7 +217,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
* 2) clients WATCHing for the destination key notified.
|
||||
* 3) The expire time of the key is reset (the key is made persistent).
|
||||
*
|
||||
* All the new keys in the database should be craeted via this interface. */
|
||||
* All the new keys in the database should be created via this interface. */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
if (lookupKeyWrite(db,key) == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
@ -230,7 +246,7 @@ robj *dbRandomKey(redisDb *db) {
|
||||
sds key;
|
||||
robj *keyobj;
|
||||
|
||||
de = dictGetRandomKey(db->dict);
|
||||
de = dictGetFairRandomKey(db->dict);
|
||||
if (de == NULL) return NULL;
|
||||
|
||||
key = dictGetKey(de);
|
||||
@ -318,7 +334,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
* If callback is given the function is called from time to time to
|
||||
* signal that work is in progress.
|
||||
*
|
||||
* The dbnum can be -1 if all teh DBs should be flushed, or the specified
|
||||
* The dbnum can be -1 if all the DBs should be flushed, or the specified
|
||||
* DB number if we want to flush only a single Redis database number.
|
||||
*
|
||||
* Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or
|
||||
@ -328,8 +344,8 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
* On success the fuction returns the number of keys removed from the
|
||||
* database(s). Otherwise -1 is returned in the specific case the
|
||||
* DB number is out of range, and errno is set to EINVAL. */
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
int j, async = (flags & EMPTYDB_ASYNC);
|
||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
|
||||
int async = (flags & EMPTYDB_ASYNC);
|
||||
long long removed = 0;
|
||||
|
||||
if (dbnum < -1 || dbnum >= server.dbnum) {
|
||||
@ -337,14 +353,26 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
if (dbnum != -1 && dbnum != j) continue;
|
||||
removed += dictSize(server.db[j].dict);
|
||||
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
|
||||
* Note that we need to call the function while the keys are still
|
||||
* there. */
|
||||
signalFlushedDb(dbnum);
|
||||
|
||||
int startdb, enddb;
|
||||
if (dbnum == -1) {
|
||||
startdb = 0;
|
||||
enddb = server.dbnum-1;
|
||||
} else {
|
||||
startdb = enddb = dbnum;
|
||||
}
|
||||
|
||||
for (int j = startdb; j <= enddb; j++) {
|
||||
removed += dictSize(dbarray[j].dict);
|
||||
if (async) {
|
||||
emptyDbAsync(&server.db[j]);
|
||||
emptyDbAsync(&dbarray[j]);
|
||||
} else {
|
||||
dictEmpty(server.db[j].dict,callback);
|
||||
dictEmpty(server.db[j].expires,callback);
|
||||
dictEmpty(dbarray[j].dict,callback);
|
||||
dictEmpty(dbarray[j].expires,callback);
|
||||
}
|
||||
}
|
||||
if (server.cluster_enabled) {
|
||||
@ -358,6 +386,10 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return removed;
|
||||
}
|
||||
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return emptyDbGeneric(server.db, dbnum, flags, callback);
|
||||
}
|
||||
|
||||
int selectDb(client *c, int id) {
|
||||
if (id < 0 || id >= server.dbnum)
|
||||
return C_ERR;
|
||||
@ -365,6 +397,15 @@ int selectDb(client *c, int id) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
long long dbTotalServerKeyCount() {
|
||||
long long total = 0;
|
||||
int j;
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
total += dictSize(server.db[j].dict);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Hooks for key space changes.
|
||||
*
|
||||
@ -376,10 +417,12 @@ int selectDb(client *c, int id) {
|
||||
|
||||
void signalModifiedKey(redisDb *db, robj *key) {
|
||||
touchWatchedKey(db,key);
|
||||
trackingInvalidateKey(key);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid) {
|
||||
touchWatchedKeysOnFlush(dbid);
|
||||
trackingInvalidateKeysOnFlush(dbid);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@ -415,7 +458,6 @@ void flushdbCommand(client *c) {
|
||||
int flags;
|
||||
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
signalFlushedDb(c->db->id);
|
||||
server.dirty += emptyDb(c->db->id,flags,NULL);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@ -427,13 +469,9 @@ void flushallCommand(client *c) {
|
||||
int flags;
|
||||
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
signalFlushedDb(-1);
|
||||
server.dirty += emptyDb(-1,flags,NULL);
|
||||
addReply(c,shared.ok);
|
||||
if (server.rdb_child_pid != -1) {
|
||||
kill(server.rdb_child_pid,SIGUSR1);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
}
|
||||
if (server.rdb_child_pid != -1) killRDBChild();
|
||||
if (server.saveparamslen > 0) {
|
||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
|
||||
@ -507,7 +545,7 @@ void randomkeyCommand(client *c) {
|
||||
robj *key;
|
||||
|
||||
if ((key = dbRandomKey(c->db)) == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -521,7 +559,7 @@ void keysCommand(client *c) {
|
||||
sds pattern = c->argv[1]->ptr;
|
||||
int plen = sdslen(pattern), allkeys;
|
||||
unsigned long numkeys = 0;
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
|
||||
di = dictGetSafeIterator(c->db->dict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
@ -531,7 +569,7 @@ void keysCommand(client *c) {
|
||||
|
||||
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
if (expireIfNeeded(c->db,keyobj) == 0) {
|
||||
if (!keyIsExpired(c->db,keyobj)) {
|
||||
addReplyBulk(c,keyobj);
|
||||
numkeys++;
|
||||
}
|
||||
@ -539,7 +577,7 @@ void keysCommand(client *c) {
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredMultiBulkLength(c,replylen,numkeys);
|
||||
setDeferredArrayLen(c,replylen,numkeys);
|
||||
}
|
||||
|
||||
/* This callback is used by scanGenericCommand in order to collect elements
|
||||
@ -593,7 +631,7 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
|
||||
}
|
||||
|
||||
/* This command implements SCAN, HSCAN and SSCAN commands.
|
||||
* If object 'o' is passed, then it must be a Hash or Set object, otherwise
|
||||
* If object 'o' is passed, then it must be a Hash, Set or Zset object, otherwise
|
||||
* if 'o' is NULL the command will operate on the dictionary associated with
|
||||
* the current database.
|
||||
*
|
||||
@ -609,6 +647,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
listNode *node, *nextnode;
|
||||
long count = 10;
|
||||
sds pat = NULL;
|
||||
sds typename = NULL;
|
||||
int patlen = 0, use_pattern = 0;
|
||||
dict *ht;
|
||||
|
||||
@ -645,6 +684,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
use_pattern = !(pat[0] == '*' && patlen == 1);
|
||||
|
||||
i += 2;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "type") && o == NULL && j >= 2) {
|
||||
/* SCAN for a particular type only applies to the db dict */
|
||||
typename = c->argv[i+1]->ptr;
|
||||
i+= 2;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
goto cleanup;
|
||||
@ -739,6 +782,13 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Filter an element if it isn't the type we want. */
|
||||
if (!filter && o == NULL && typename){
|
||||
robj* typecheck = lookupKeyReadWithFlags(c->db, kobj, LOOKUP_NOTOUCH);
|
||||
char* type = getObjectTypeName(typecheck);
|
||||
if (strcasecmp((char*) typename, type)) filter = 1;
|
||||
}
|
||||
|
||||
/* Filter element if it is an expired key. */
|
||||
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
|
||||
|
||||
@ -764,10 +814,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
}
|
||||
|
||||
/* Step 4: Reply to the client. */
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyBulkLongLong(c,cursor);
|
||||
|
||||
addReplyMultiBulkLen(c, listLength(keys));
|
||||
addReplyArrayLen(c, listLength(keys));
|
||||
while ((node = listFirst(keys)) != NULL) {
|
||||
robj *kobj = listNodeValue(node);
|
||||
addReplyBulk(c, kobj);
|
||||
@ -795,11 +845,8 @@ void lastsaveCommand(client *c) {
|
||||
addReplyLongLong(c,server.lastsave);
|
||||
}
|
||||
|
||||
void typeCommand(client *c) {
|
||||
robj *o;
|
||||
char *type;
|
||||
|
||||
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
|
||||
char* getObjectTypeName(robj *o) {
|
||||
char* type;
|
||||
if (o == NULL) {
|
||||
type = "none";
|
||||
} else {
|
||||
@ -817,7 +864,13 @@ void typeCommand(client *c) {
|
||||
default: type = "unknown"; break;
|
||||
}
|
||||
}
|
||||
addReplyStatus(c,type);
|
||||
return type;
|
||||
}
|
||||
|
||||
void typeCommand(client *c) {
|
||||
robj *o;
|
||||
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
|
||||
addReplyStatus(c, getObjectTypeName(o));
|
||||
}
|
||||
|
||||
void shutdownCommand(client *c) {
|
||||
@ -979,7 +1032,7 @@ void scanDatabaseForReadyLists(redisDb *db) {
|
||||
*
|
||||
* Returns C_ERR if at least one of the DB ids are out of range, otherwise
|
||||
* C_OK is returned. */
|
||||
int dbSwapDatabases(int id1, int id2) {
|
||||
int dbSwapDatabases(long id1, long id2) {
|
||||
if (id1 < 0 || id1 >= server.dbnum ||
|
||||
id2 < 0 || id2 >= server.dbnum) return C_ERR;
|
||||
if (id1 == id2) return C_OK;
|
||||
@ -1108,6 +1161,25 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
decrRefCount(argv[1]);
|
||||
}
|
||||
|
||||
/* Check if the key is expired. */
|
||||
int keyIsExpired(redisDb *db, robj *key) {
|
||||
mstime_t when = getExpire(db,key);
|
||||
|
||||
if (when < 0) return 0; /* No expire for this key */
|
||||
|
||||
/* Don't expire anything while loading. It will be done later. */
|
||||
if (server.loading) return 0;
|
||||
|
||||
/* If we are in the context of a Lua script, we pretend that time is
|
||||
* blocked to when the Lua script started. This way a key can expire
|
||||
* only the first time it is accessed and not in the middle of the
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
|
||||
|
||||
return now > when;
|
||||
}
|
||||
|
||||
/* This function is called when we are going to perform some operation
|
||||
* in a given key, but such key may be already logically expired even if
|
||||
* it still exists in the database. The main way this function is called
|
||||
@ -1128,32 +1200,17 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
* The return value of the function is 0 if the key is still valid,
|
||||
* otherwise the function returns 1 if the key is expired. */
|
||||
int expireIfNeeded(redisDb *db, robj *key) {
|
||||
mstime_t when = getExpire(db,key);
|
||||
mstime_t now;
|
||||
if (!keyIsExpired(db,key)) return 0;
|
||||
|
||||
if (when < 0) return 0; /* No expire for this key */
|
||||
|
||||
/* Don't expire anything while loading. It will be done later. */
|
||||
if (server.loading) return 0;
|
||||
|
||||
/* If we are in the context of a Lua script, we pretend that time is
|
||||
* blocked to when the Lua script started. This way a key can expire
|
||||
* only the first time it is accessed and not in the middle of the
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
now = server.lua_caller ? server.lua_time_start : mstime();
|
||||
|
||||
/* If we are running in the context of a slave, return ASAP:
|
||||
/* If we are running in the context of a slave, instead of
|
||||
* evicting the expired key from the database, we return ASAP:
|
||||
* the slave key expiration is controlled by the master that will
|
||||
* send us synthesized DEL operations for expired keys.
|
||||
*
|
||||
* Still we try to return the right information to the caller,
|
||||
* that is, 0 if we think the key should be still valid, 1 if
|
||||
* we think the key is expired at this time. */
|
||||
if (server.masterhost != NULL) return now > when;
|
||||
|
||||
/* Return when this key has not expired */
|
||||
if (now <= when) return 0;
|
||||
if (server.masterhost != NULL) return 1;
|
||||
|
||||
/* Delete the key */
|
||||
server.stat_expiredkeys++;
|
||||
@ -1185,7 +1242,7 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
if (j >= argc) {
|
||||
/* Modules commands, and standard commands with a not fixed number
|
||||
* of arugments (negative arity parameter) do not have dispatch
|
||||
* of arguments (negative arity parameter) do not have dispatch
|
||||
* time arity checks, so we need to handle the case where the user
|
||||
* passed an invalid number of arguments here. In this case we
|
||||
* return no keys and expect the command implementation to report
|
||||
@ -1400,7 +1457,7 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
/* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>]
|
||||
* STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */
|
||||
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, *keys;
|
||||
int i, num = 0, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
/* We need to parse the options of the command in order to seek the first
|
||||
|
577
src/debug.c
577
src/debug.c
@ -37,7 +37,11 @@
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#include <execinfo.h>
|
||||
#ifndef __OpenBSD__
|
||||
#include <ucontext.h>
|
||||
#else
|
||||
typedef ucontext_t sigcontext_t;
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
#include "bio.h"
|
||||
#include <unistd.h>
|
||||
@ -70,7 +74,7 @@ void xorDigest(unsigned char *digest, void *ptr, size_t len) {
|
||||
digest[j] ^= hash[j];
|
||||
}
|
||||
|
||||
void xorObjectDigest(unsigned char *digest, robj *o) {
|
||||
void xorStringObjectDigest(unsigned char *digest, robj *o) {
|
||||
o = getDecodedObject(o);
|
||||
xorDigest(digest,o->ptr,sdslen(o->ptr));
|
||||
decrRefCount(o);
|
||||
@ -100,12 +104,151 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len) {
|
||||
SHA1Final(digest,&ctx);
|
||||
}
|
||||
|
||||
void mixObjectDigest(unsigned char *digest, robj *o) {
|
||||
void mixStringObjectDigest(unsigned char *digest, robj *o) {
|
||||
o = getDecodedObject(o);
|
||||
mixDigest(digest,o->ptr,sdslen(o->ptr));
|
||||
decrRefCount(o);
|
||||
}
|
||||
|
||||
/* This function computes the digest of a data structure stored in the
|
||||
* object 'o'. It is the core of the DEBUG DIGEST command: when taking the
|
||||
* digest of a whole dataset, we take the digest of the key and the value
|
||||
* pair, and xor all those together.
|
||||
*
|
||||
* Note that this function does not reset the initial 'digest' passed, it
|
||||
* will continue mixing this object digest to anything that was already
|
||||
* present. */
|
||||
void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) {
|
||||
uint32_t aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
long long expiretime = getExpire(db,keyobj);
|
||||
char buf[128];
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
mixStringObjectDigest(digest,o);
|
||||
} else if (o->type == OBJ_LIST) {
|
||||
listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
|
||||
listTypeEntry entry;
|
||||
while(listTypeNext(li,&entry)) {
|
||||
robj *eleobj = listTypeGet(&entry);
|
||||
mixStringObjectDigest(digest,eleobj);
|
||||
decrRefCount(eleobj);
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
} else if (o->type == OBJ_SET) {
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
sds sdsele;
|
||||
while((sdsele = setTypeNextObject(si)) != NULL) {
|
||||
xorDigest(digest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
unsigned char eledigest[20];
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = o->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vll;
|
||||
double score;
|
||||
|
||||
eptr = ziplistIndex(zl,0);
|
||||
serverAssert(eptr != NULL);
|
||||
sptr = ziplistNext(zl,eptr);
|
||||
serverAssert(sptr != NULL);
|
||||
|
||||
while (eptr != NULL) {
|
||||
serverAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
|
||||
score = zzlGetScore(sptr);
|
||||
|
||||
memset(eledigest,0,20);
|
||||
if (vstr != NULL) {
|
||||
mixDigest(eledigest,vstr,vlen);
|
||||
} else {
|
||||
ll2string(buf,sizeof(buf),vll);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
}
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",score);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
zzlNext(zl,&eptr,&sptr);
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
double *score = dictGetVal(de);
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",*score);
|
||||
memset(eledigest,0,20);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
hashTypeIterator *hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
unsigned char eledigest[20];
|
||||
sds sdsele;
|
||||
|
||||
memset(eledigest,0,20);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
hashTypeReleaseIterator(hi);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,o->ptr,NULL,NULL,0);
|
||||
streamID id;
|
||||
int64_t numfields;
|
||||
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
|
||||
mixDigest(digest,itemid,sdslen(itemid));
|
||||
sdsfree(itemid);
|
||||
|
||||
while(numfields--) {
|
||||
unsigned char *field, *value;
|
||||
int64_t field_len, value_len;
|
||||
streamIteratorGetField(&si,&field,&value,
|
||||
&field_len,&value_len);
|
||||
mixDigest(digest,field,field_len);
|
||||
mixDigest(digest,value,value_len);
|
||||
}
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
RedisModuleDigest md;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitDigestContext(md);
|
||||
if (mt->digest) {
|
||||
mt->digest(&md,mv->value);
|
||||
xorDigest(digest,md.x,sizeof(md.x));
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
/* If the key has an expire, add it to the mix */
|
||||
if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
|
||||
}
|
||||
|
||||
/* Compute the dataset digest. Since keys, sets elements, hashes elements
|
||||
* are not ordered, we use a trick: every aggregate digest is the xor
|
||||
* of the digests of their elements. This way the order will not change
|
||||
@ -114,7 +257,6 @@ void mixObjectDigest(unsigned char *digest, robj *o) {
|
||||
* a different digest. */
|
||||
void computeDatasetDigest(unsigned char *final) {
|
||||
unsigned char digest[20];
|
||||
char buf[128];
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
@ -137,7 +279,6 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key;
|
||||
robj *keyobj, *o;
|
||||
long long expiretime;
|
||||
|
||||
memset(digest,0,20); /* This key-val digest */
|
||||
key = dictGetKey(de);
|
||||
@ -146,134 +287,8 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
mixDigest(digest,key,sdslen(key));
|
||||
|
||||
o = dictGetVal(de);
|
||||
xorObjectDigest(db,keyobj,digest,o);
|
||||
|
||||
aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
expiretime = getExpire(db,keyobj);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
mixObjectDigest(digest,o);
|
||||
} else if (o->type == OBJ_LIST) {
|
||||
listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
|
||||
listTypeEntry entry;
|
||||
while(listTypeNext(li,&entry)) {
|
||||
robj *eleobj = listTypeGet(&entry);
|
||||
mixObjectDigest(digest,eleobj);
|
||||
decrRefCount(eleobj);
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
} else if (o->type == OBJ_SET) {
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
sds sdsele;
|
||||
while((sdsele = setTypeNextObject(si)) != NULL) {
|
||||
xorDigest(digest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
unsigned char eledigest[20];
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = o->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vll;
|
||||
double score;
|
||||
|
||||
eptr = ziplistIndex(zl,0);
|
||||
serverAssert(eptr != NULL);
|
||||
sptr = ziplistNext(zl,eptr);
|
||||
serverAssert(sptr != NULL);
|
||||
|
||||
while (eptr != NULL) {
|
||||
serverAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
|
||||
score = zzlGetScore(sptr);
|
||||
|
||||
memset(eledigest,0,20);
|
||||
if (vstr != NULL) {
|
||||
mixDigest(eledigest,vstr,vlen);
|
||||
} else {
|
||||
ll2string(buf,sizeof(buf),vll);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
}
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",score);
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
zzlNext(zl,&eptr,&sptr);
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
double *score = dictGetVal(de);
|
||||
|
||||
snprintf(buf,sizeof(buf),"%.17g",*score);
|
||||
memset(eledigest,0,20);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
mixDigest(eledigest,buf,strlen(buf));
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
hashTypeIterator *hi = hashTypeInitIterator(o);
|
||||
while (hashTypeNext(hi) != C_ERR) {
|
||||
unsigned char eledigest[20];
|
||||
sds sdsele;
|
||||
|
||||
memset(eledigest,0,20);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
|
||||
mixDigest(eledigest,sdsele,sdslen(sdsele));
|
||||
sdsfree(sdsele);
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
hashTypeReleaseIterator(hi);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,o->ptr,NULL,NULL,0);
|
||||
streamID id;
|
||||
int64_t numfields;
|
||||
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
|
||||
mixDigest(digest,itemid,sdslen(itemid));
|
||||
sdsfree(itemid);
|
||||
|
||||
while(numfields--) {
|
||||
unsigned char *field, *value;
|
||||
int64_t field_len, value_len;
|
||||
streamIteratorGetField(&si,&field,&value,
|
||||
&field_len,&value_len);
|
||||
mixDigest(digest,field,field_len);
|
||||
mixDigest(digest,value,value_len);
|
||||
}
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
RedisModuleDigest md;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitDigestContext(md);
|
||||
if (mt->digest) {
|
||||
mt->digest(&md,mv->value);
|
||||
xorDigest(digest,md.x,sizeof(md.x));
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
/* If the key has an expire, add it to the mix */
|
||||
if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
|
||||
/* We can finally xor the key-val digest to the final digest */
|
||||
xorDigest(final,digest,20);
|
||||
decrRefCount(keyobj);
|
||||
@ -285,26 +300,29 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
void debugCommand(client *c) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"assert -- Crash by assertion failed.",
|
||||
"change-repl-id -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.",
|
||||
"crash-and-recover <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
|
||||
"digest -- Outputs an hex signature representing the current DB content.",
|
||||
"htstats <dbid> -- Return hash table statistics of the specified Redis database.",
|
||||
"htstats-key <key> -- Like htstats but for the hash table stored as key's value.",
|
||||
"loadaof -- Flush the AOF buffers on disk and reload the AOF in memory.",
|
||||
"lua-always-replicate-commands (0|1) -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.",
|
||||
"object <key> -- Show low level info about key and associated value.",
|
||||
"panic -- Crash the server simulating a panic.",
|
||||
"populate <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
|
||||
"reload -- Save the RDB on disk and reload it back in memory.",
|
||||
"restart -- Graceful restart: save config, db, restart.",
|
||||
"sdslen <key> -- Show low level SDS string info representing key and value.",
|
||||
"segfault -- Crash the server with sigsegv.",
|
||||
"set-active-expire (0|1) -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
|
||||
"sleep <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
||||
"structsize -- Return the size of different Redis core C structures.",
|
||||
"ziplist <key> -- Show low level info about the ziplist encoding.",
|
||||
"error <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
|
||||
"ASSERT -- Crash by assertion failed.",
|
||||
"CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.",
|
||||
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
|
||||
"DIGEST -- Output a hex signature representing the current DB content.",
|
||||
"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.",
|
||||
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
|
||||
"LOG <message> -- write message to the server log.",
|
||||
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
|
||||
"HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.",
|
||||
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
|
||||
"LUA-ALWAYS-REPLICATE-COMMANDS <0|1> -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.",
|
||||
"OBJECT <key> -- Show low level info about key and associated value.",
|
||||
"PANIC -- Crash the server simulating a panic.",
|
||||
"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
|
||||
"RELOAD -- Save the RDB on disk and reload it back in memory.",
|
||||
"RESTART -- Graceful restart: save config, db, restart.",
|
||||
"SDSLEN <key> -- Show low level SDS string info representing key and value.",
|
||||
"SEGFAULT -- Crash the server with sigsegv.",
|
||||
"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
|
||||
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
||||
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -331,8 +349,10 @@ NULL
|
||||
zfree(ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"assert")) {
|
||||
if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
serverAssertWithInfo(c,c->argv[0],1 == 2);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"log") && c->argc == 3) {
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
@ -341,7 +361,10 @@ NULL
|
||||
return;
|
||||
}
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
if (rdbLoad(server.rdb_filename,NULL) != C_OK) {
|
||||
protectClient(c);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
return;
|
||||
}
|
||||
@ -350,7 +373,10 @@ NULL
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
|
||||
if (server.aof_state != AOF_OFF) flushAppendOnlyFile(1);
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
if (loadAppendOnlyFile(server.aof_filename) != C_OK) {
|
||||
protectClient(c);
|
||||
int ret = loadAppendOnlyFile(server.aof_filename);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
@ -481,15 +507,80 @@ NULL
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
|
||||
/* DEBUG DIGEST (form without keys specified) */
|
||||
unsigned char digest[20];
|
||||
sds d = sdsempty();
|
||||
int j;
|
||||
|
||||
computeDatasetDigest(digest);
|
||||
for (j = 0; j < 20; j++)
|
||||
d = sdscatprintf(d, "%02x",digest[j]);
|
||||
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
|
||||
addReplyStatus(c,d);
|
||||
sdsfree(d);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"digest-value") && c->argc >= 2) {
|
||||
/* DEBUG DIGEST-VALUE key key key ... key. */
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
unsigned char digest[20];
|
||||
memset(digest,0,20); /* Start with a clean result */
|
||||
robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH);
|
||||
if (o) xorObjectDigest(c->db,c->argv[j],digest,o);
|
||||
|
||||
sds d = sdsempty();
|
||||
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
|
||||
addReplyStatus(c,d);
|
||||
sdsfree(d);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"protocol") && c->argc == 3) {
|
||||
/* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|
|
||||
* attrib|push|verbatim|true|false|state|err|bloberr] */
|
||||
char *name = c->argv[2]->ptr;
|
||||
if (!strcasecmp(name,"string")) {
|
||||
addReplyBulkCString(c,"Hello World");
|
||||
} else if (!strcasecmp(name,"integer")) {
|
||||
addReplyLongLong(c,12345);
|
||||
} else if (!strcasecmp(name,"double")) {
|
||||
addReplyDouble(c,3.14159265359);
|
||||
} else if (!strcasecmp(name,"bignum")) {
|
||||
addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
|
||||
} else if (!strcasecmp(name,"null")) {
|
||||
addReplyNull(c);
|
||||
} else if (!strcasecmp(name,"array")) {
|
||||
addReplyArrayLen(c,3);
|
||||
for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
|
||||
} else if (!strcasecmp(name,"set")) {
|
||||
addReplySetLen(c,3);
|
||||
for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
|
||||
} else if (!strcasecmp(name,"map")) {
|
||||
addReplyMapLen(c,3);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
addReplyLongLong(c,j);
|
||||
addReplyBool(c, j == 1);
|
||||
}
|
||||
} else if (!strcasecmp(name,"attrib")) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
/* Attributes are not real replies, so a well formed reply should
|
||||
* also have a normal reply type after the attribute. */
|
||||
addReplyBulkCString(c,"Some real reply following the attribute");
|
||||
} else if (!strcasecmp(name,"push")) {
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCString(c,"server-cpu-usage");
|
||||
addReplyLongLong(c,42);
|
||||
/* Push replies are not synchronous replies, so we emit also a
|
||||
* normal reply in order for blocking clients just discarding the
|
||||
* push reply, to actually consume the reply and continue. */
|
||||
addReplyBulkCString(c,"Some real reply following the push reply");
|
||||
} else if (!strcasecmp(name,"true")) {
|
||||
addReplyBool(c,1);
|
||||
} else if (!strcasecmp(name,"false")) {
|
||||
addReplyBool(c,0);
|
||||
} else if (!strcasecmp(name,"verbatim")) {
|
||||
addReplyVerbatim(c,"This is a verbatim\nstring",25,"txt");
|
||||
} else {
|
||||
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false|state|err|bloberr");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(c->argv[2]->ptr,NULL);
|
||||
long long utime = dtime*1000000;
|
||||
@ -581,9 +672,12 @@ NULL
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stringmatch-test") && c->argc == 2)
|
||||
{
|
||||
stringmatchlen_fuzz_test();
|
||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try DEBUG HELP",
|
||||
(char*)c->argv[1]->ptr);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -608,7 +702,7 @@ void _serverAssertPrintClientInfo(const client *c) {
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
|
||||
serverLog(LL_WARNING,"client->flags = %d", c->flags);
|
||||
serverLog(LL_WARNING,"client->flags = %llu", (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++) {
|
||||
@ -709,7 +803,7 @@ static void *getMcontextEip(ucontext_t *uc) {
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
/* Linux */
|
||||
#if defined(__i386__)
|
||||
#if defined(__i386__) || defined(__ILP32__)
|
||||
return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
|
||||
@ -720,6 +814,22 @@ static void *getMcontextEip(ucontext_t *uc) {
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
return (void*) uc->uc_mcontext.pc;
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
/* FreeBSD */
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->uc_mcontext.mc_eip;
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
#endif
|
||||
#elif defined(__OpenBSD__)
|
||||
/* OpenBSD */
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->sc_eip;
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->sc_rip;
|
||||
#endif
|
||||
#elif defined(__DragonFly__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
@ -805,7 +915,7 @@ void logRegisters(ucontext_t *uc) {
|
||||
/* Linux */
|
||||
#elif defined(__linux__)
|
||||
/* Linux x86 */
|
||||
#if defined(__i386__)
|
||||
#if defined(__i386__) || defined(__ILP32__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
|
||||
@ -861,6 +971,145 @@ void logRegisters(ucontext_t *uc) {
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[15]);
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
#if defined(__x86_64__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
|
||||
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
|
||||
(unsigned long) uc->uc_mcontext.mc_rax,
|
||||
(unsigned long) uc->uc_mcontext.mc_rbx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rcx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rdx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rdi,
|
||||
(unsigned long) uc->uc_mcontext.mc_rsi,
|
||||
(unsigned long) uc->uc_mcontext.mc_rbp,
|
||||
(unsigned long) uc->uc_mcontext.mc_rsp,
|
||||
(unsigned long) uc->uc_mcontext.mc_r8,
|
||||
(unsigned long) uc->uc_mcontext.mc_r9,
|
||||
(unsigned long) uc->uc_mcontext.mc_r10,
|
||||
(unsigned long) uc->uc_mcontext.mc_r11,
|
||||
(unsigned long) uc->uc_mcontext.mc_r12,
|
||||
(unsigned long) uc->uc_mcontext.mc_r13,
|
||||
(unsigned long) uc->uc_mcontext.mc_r14,
|
||||
(unsigned long) uc->uc_mcontext.mc_r15,
|
||||
(unsigned long) uc->uc_mcontext.mc_rip,
|
||||
(unsigned long) uc->uc_mcontext.mc_rflags,
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#elif defined(__i386__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
|
||||
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
|
||||
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
|
||||
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
|
||||
(unsigned long) uc->uc_mcontext.mc_eax,
|
||||
(unsigned long) uc->uc_mcontext.mc_ebx,
|
||||
(unsigned long) uc->uc_mcontext.mc_ebx,
|
||||
(unsigned long) uc->uc_mcontext.mc_edx,
|
||||
(unsigned long) uc->uc_mcontext.mc_edi,
|
||||
(unsigned long) uc->uc_mcontext.mc_esi,
|
||||
(unsigned long) uc->uc_mcontext.mc_ebp,
|
||||
(unsigned long) uc->uc_mcontext.mc_esp,
|
||||
(unsigned long) uc->uc_mcontext.mc_ss,
|
||||
(unsigned long) uc->uc_mcontext.mc_eflags,
|
||||
(unsigned long) uc->uc_mcontext.mc_eip,
|
||||
(unsigned long) uc->uc_mcontext.mc_cs,
|
||||
(unsigned long) uc->uc_mcontext.mc_es,
|
||||
(unsigned long) uc->uc_mcontext.mc_fs,
|
||||
(unsigned long) uc->uc_mcontext.mc_gs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_esp);
|
||||
#endif
|
||||
#elif defined(__OpenBSD__)
|
||||
#if defined(__x86_64__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
|
||||
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
|
||||
(unsigned long) uc->sc_rax,
|
||||
(unsigned long) uc->sc_rbx,
|
||||
(unsigned long) uc->sc_rcx,
|
||||
(unsigned long) uc->sc_rdx,
|
||||
(unsigned long) uc->sc_rdi,
|
||||
(unsigned long) uc->sc_rsi,
|
||||
(unsigned long) uc->sc_rbp,
|
||||
(unsigned long) uc->sc_rsp,
|
||||
(unsigned long) uc->sc_r8,
|
||||
(unsigned long) uc->sc_r9,
|
||||
(unsigned long) uc->sc_r10,
|
||||
(unsigned long) uc->sc_r11,
|
||||
(unsigned long) uc->sc_r12,
|
||||
(unsigned long) uc->sc_r13,
|
||||
(unsigned long) uc->sc_r14,
|
||||
(unsigned long) uc->sc_r15,
|
||||
(unsigned long) uc->sc_rip,
|
||||
(unsigned long) uc->sc_rflags,
|
||||
(unsigned long) uc->sc_cs
|
||||
);
|
||||
logStackContent((void**)uc->sc_rsp);
|
||||
#elif defined(__i386__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
|
||||
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
|
||||
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
|
||||
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
|
||||
(unsigned long) uc->sc_eax,
|
||||
(unsigned long) uc->sc_ebx,
|
||||
(unsigned long) uc->sc_ebx,
|
||||
(unsigned long) uc->sc_edx,
|
||||
(unsigned long) uc->sc_edi,
|
||||
(unsigned long) uc->sc_esi,
|
||||
(unsigned long) uc->sc_ebp,
|
||||
(unsigned long) uc->sc_esp,
|
||||
(unsigned long) uc->sc_ss,
|
||||
(unsigned long) uc->sc_eflags,
|
||||
(unsigned long) uc->sc_eip,
|
||||
(unsigned long) uc->sc_cs,
|
||||
(unsigned long) uc->sc_es,
|
||||
(unsigned long) uc->sc_fs,
|
||||
(unsigned long) uc->sc_gs
|
||||
);
|
||||
logStackContent((void**)uc->sc_esp);
|
||||
#endif
|
||||
#elif defined(__DragonFly__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
|
||||
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
|
||||
(unsigned long) uc->uc_mcontext.mc_rax,
|
||||
(unsigned long) uc->uc_mcontext.mc_rbx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rcx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rdx,
|
||||
(unsigned long) uc->uc_mcontext.mc_rdi,
|
||||
(unsigned long) uc->uc_mcontext.mc_rsi,
|
||||
(unsigned long) uc->uc_mcontext.mc_rbp,
|
||||
(unsigned long) uc->uc_mcontext.mc_rsp,
|
||||
(unsigned long) uc->uc_mcontext.mc_r8,
|
||||
(unsigned long) uc->uc_mcontext.mc_r9,
|
||||
(unsigned long) uc->uc_mcontext.mc_r10,
|
||||
(unsigned long) uc->uc_mcontext.mc_r11,
|
||||
(unsigned long) uc->uc_mcontext.mc_r12,
|
||||
(unsigned long) uc->uc_mcontext.mc_r13,
|
||||
(unsigned long) uc->uc_mcontext.mc_r14,
|
||||
(unsigned long) uc->uc_mcontext.mc_r15,
|
||||
(unsigned long) uc->uc_mcontext.mc_rip,
|
||||
(unsigned long) uc->uc_mcontext.mc_rflags,
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#else
|
||||
serverLog(LL_WARNING,
|
||||
" Dumping of registers not supported for this OS/arch");
|
||||
@ -1077,7 +1326,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
infostring = genRedisInfoString("all");
|
||||
serverLogRaw(LL_WARNING|LL_RAW, infostring);
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ CLIENT LIST OUTPUT ------\n");
|
||||
clients = getAllClientsInfoString();
|
||||
clients = getAllClientsInfoString(-1);
|
||||
serverLogRaw(LL_WARNING|LL_RAW, clients);
|
||||
sdsfree(infostring);
|
||||
sdsfree(clients);
|
||||
@ -1180,6 +1429,8 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len) {
|
||||
void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) {
|
||||
#ifdef HAVE_BACKTRACE
|
||||
ucontext_t *uc = (ucontext_t*) secret;
|
||||
#else
|
||||
(void)secret;
|
||||
#endif
|
||||
UNUSED(info);
|
||||
UNUSED(sig);
|
||||
|
221
src/defrag.c
221
src/defrag.c
@ -47,7 +47,7 @@ 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, unsigned int hash, long *defragged);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
@ -355,7 +355,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
sdsele = ln->value;
|
||||
if ((newsds = activeDefragSds(sdsele))) {
|
||||
/* When defragging an sds value, we need to update the dict key */
|
||||
unsigned int hash = dictGetHash(d, sdsele);
|
||||
uint64_t hash = dictGetHash(d, sdsele);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
|
||||
ln->value = newsds;
|
||||
defragged++;
|
||||
@ -392,7 +392,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
* moved. Return value is the the dictEntry if found, or NULL if not found.
|
||||
* NOTE: this is very ugly code, but it let's us avoid the complication of
|
||||
* doing a scan on another dict. */
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, long *defragged) {
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged) {
|
||||
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
|
||||
if (deref) {
|
||||
dictEntry *de = *deref;
|
||||
@ -592,6 +592,171 @@ long defragSet(redisDb *db, dictEntry *kde) {
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Defrag callback for radix tree iterator, called for each node,
|
||||
* used in order to defrag the nodes allocations. */
|
||||
int defragRaxNode(raxNode **noderef) {
|
||||
raxNode *newnode = activeDefragAlloc(*noderef);
|
||||
if (newnode) {
|
||||
*noderef = newnode;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime, long long *defragged) {
|
||||
static unsigned char last[sizeof(streamID)];
|
||||
raxIterator ri;
|
||||
long iterations = 0;
|
||||
if (ob->type != OBJ_STREAM || ob->encoding != OBJ_ENCODING_STREAM) {
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream *s = ob->ptr;
|
||||
raxStart(&ri,s->rax);
|
||||
if (*cursor == 0) {
|
||||
/* if cursor is 0, we start new iteration */
|
||||
defragRaxNode(&s->rax->head);
|
||||
/* assign the iterator node callback before the seek, so that the
|
||||
* initial nodes that are processed till the first item are covered */
|
||||
ri.node_cb = defragRaxNode;
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
} else {
|
||||
/* if cursor is non-zero, we seek to the static 'last' */
|
||||
if (!raxSeek(&ri,">", last, sizeof(last))) {
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
/* assign the iterator node callback after the seek, so that the
|
||||
* initial nodes that are processed till now aren't covered */
|
||||
ri.node_cb = defragRaxNode;
|
||||
}
|
||||
|
||||
(*cursor)++;
|
||||
while (raxNext(&ri)) {
|
||||
void *newdata = activeDefragAlloc(ri.data);
|
||||
if (newdata)
|
||||
raxSetData(ri.node, ri.data=newdata), (*defragged)++;
|
||||
if (++iterations > 16) {
|
||||
if (ustime() > endtime) {
|
||||
serverAssert(ri.key_len==sizeof(last));
|
||||
memcpy(last,ri.key,ri.key_len);
|
||||
raxStop(&ri);
|
||||
return 1;
|
||||
}
|
||||
iterations = 0;
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* optional callback used defrag each rax element (not including the element pointer itself) */
|
||||
typedef void *(raxDefragFunction)(raxIterator *ri, void *privdata, long *defragged);
|
||||
|
||||
/* defrag radix tree including:
|
||||
* 1) rax struct
|
||||
* 2) rax nodes
|
||||
* 3) rax entry data (only if defrag_data is specified)
|
||||
* 4) call a callback per element, and allow the callback to return a new pointer for the element */
|
||||
long defragRadixTree(rax **raxref, int defrag_data, raxDefragFunction *element_cb, void *element_cb_data) {
|
||||
long defragged = 0;
|
||||
raxIterator ri;
|
||||
rax* rax;
|
||||
if ((rax = activeDefragAlloc(*raxref)))
|
||||
defragged++, *raxref = rax;
|
||||
rax = *raxref;
|
||||
raxStart(&ri,rax);
|
||||
ri.node_cb = defragRaxNode;
|
||||
defragRaxNode(&rax->head);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while (raxNext(&ri)) {
|
||||
void *newdata = NULL;
|
||||
if (element_cb)
|
||||
newdata = element_cb(&ri, element_cb_data, &defragged);
|
||||
if (defrag_data && !newdata)
|
||||
newdata = activeDefragAlloc(ri.data);
|
||||
if (newdata)
|
||||
raxSetData(ri.node, ri.data=newdata), defragged++;
|
||||
}
|
||||
raxStop(&ri);
|
||||
return defragged;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
streamCG *cg;
|
||||
streamConsumer *c;
|
||||
} PendingEntryContext;
|
||||
|
||||
void* defragStreamConsumerPendingEntry(raxIterator *ri, void *privdata, long *defragged) {
|
||||
UNUSED(defragged);
|
||||
PendingEntryContext *ctx = privdata;
|
||||
streamNACK *nack = ri->data, *newnack;
|
||||
nack->consumer = ctx->c; /* update nack pointer to consumer */
|
||||
newnack = activeDefragAlloc(nack);
|
||||
if (newnack) {
|
||||
/* update consumer group pointer to the nack */
|
||||
void *prev;
|
||||
raxInsert(ctx->cg->pel, ri->key, ri->key_len, newnack, &prev);
|
||||
serverAssert(prev==nack);
|
||||
/* note: we don't increment 'defragged' that's done by the caller */
|
||||
}
|
||||
return newnack;
|
||||
}
|
||||
|
||||
void* defragStreamConsumer(raxIterator *ri, void *privdata, long *defragged) {
|
||||
streamConsumer *c = ri->data;
|
||||
streamCG *cg = privdata;
|
||||
void *newc = activeDefragAlloc(c);
|
||||
if (newc) {
|
||||
/* note: we don't increment 'defragged' that's done by the caller */
|
||||
c = newc;
|
||||
}
|
||||
sds newsds = activeDefragSds(c->name);
|
||||
if (newsds)
|
||||
(*defragged)++, c->name = newsds;
|
||||
if (c->pel) {
|
||||
PendingEntryContext pel_ctx = {cg, c};
|
||||
*defragged += defragRadixTree(&c->pel, 0, defragStreamConsumerPendingEntry, &pel_ctx);
|
||||
}
|
||||
return newc; /* returns NULL if c was not defragged */
|
||||
}
|
||||
|
||||
void* defragStreamConsumerGroup(raxIterator *ri, void *privdata, long *defragged) {
|
||||
streamCG *cg = ri->data;
|
||||
UNUSED(privdata);
|
||||
if (cg->consumers)
|
||||
*defragged += defragRadixTree(&cg->consumers, 0, defragStreamConsumer, cg);
|
||||
if (cg->pel)
|
||||
*defragged += defragRadixTree(&cg->pel, 0, NULL, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long defragStream(redisDb *db, dictEntry *kde) {
|
||||
long defragged = 0;
|
||||
robj *ob = dictGetVal(kde);
|
||||
serverAssert(ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM);
|
||||
stream *s = ob->ptr, *news;
|
||||
|
||||
/* handle the main struct */
|
||||
if ((news = activeDefragAlloc(s)))
|
||||
defragged++, ob->ptr = s = news;
|
||||
|
||||
if (raxSize(s->rax) > server.active_defrag_max_scan_fields) {
|
||||
rax *newrax = activeDefragAlloc(s->rax);
|
||||
if (newrax)
|
||||
defragged++, s->rax = newrax;
|
||||
defragLater(db, kde);
|
||||
} else
|
||||
defragged += defragRadixTree(&s->rax, 1, NULL, NULL);
|
||||
|
||||
if (s->cgroups)
|
||||
defragged += defragRadixTree(&s->cgroups, 1, defragStreamConsumerGroup, NULL);
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* for each key we scan in the main dict, this function will attempt to defrag
|
||||
* all the various pointers it has. Returns a stat of how many pointers were
|
||||
* moved. */
|
||||
@ -660,6 +825,8 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
defragged += defragStream(db, de);
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
/* Currently defragmenting modules private data types
|
||||
* is not supported. */
|
||||
@ -680,7 +847,7 @@ void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
server.stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for for each hash table bicket,
|
||||
/* Defrag scan callback for each hash table bicket,
|
||||
* 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 */
|
||||
@ -700,9 +867,8 @@ void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
* or not, a false detection can cause the defragmenter to waste a lot of CPU
|
||||
* without the possibility of getting any results. */
|
||||
float getAllocatorFragmentation(size_t *out_frag_bytes) {
|
||||
size_t resident = server.cron_malloc_stats.allocator_resident;
|
||||
size_t active = server.cron_malloc_stats.allocator_active;
|
||||
size_t allocated = server.cron_malloc_stats.allocator_allocated;
|
||||
size_t resident, active, allocated;
|
||||
zmalloc_get_allocator_info(&allocated, &active, &resident);
|
||||
float frag_pct = ((float)active / allocated)*100 - 100;
|
||||
size_t frag_bytes = active - allocated;
|
||||
float rss_pct = ((float)resident / allocated)*100 - 100;
|
||||
@ -728,27 +894,29 @@ long defragOtherGlobals() {
|
||||
return defragged;
|
||||
}
|
||||
|
||||
unsigned long defragLaterItem(dictEntry *de, unsigned long cursor) {
|
||||
long defragged = 0;
|
||||
/* 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 = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
defragged += scanLaterList(ob);
|
||||
cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
server.stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
defragged += scanLaterSet(ob, &cursor);
|
||||
server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
defragged += scanLaterZset(ob, &cursor);
|
||||
server.stat_active_defrag_hits += scanLaterZset(ob, cursor);
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
defragged += scanLaterHash(ob, &cursor);
|
||||
server.stat_active_defrag_hits += scanLaterHash(ob, cursor);
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
return scanLaterStraemListpacks(ob, cursor, endtime, &server.stat_active_defrag_hits);
|
||||
} else {
|
||||
cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
*cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
}
|
||||
} else {
|
||||
cursor = 0; /* object may have been deleted already */
|
||||
*cursor = 0; /* object may have been deleted already */
|
||||
}
|
||||
server.stat_active_defrag_hits += defragged;
|
||||
return cursor;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
@ -788,17 +956,22 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
dictEntry *de = dictFind(db->dict, current_key);
|
||||
key_defragged = server.stat_active_defrag_hits;
|
||||
do {
|
||||
cursor = defragLaterItem(de, cursor);
|
||||
int quit = 0;
|
||||
if (defragLaterItem(de, &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
|
||||
* next key can be a list, and scanLaterList must be done in once cycle */
|
||||
if (!cursor)
|
||||
quit = 1;
|
||||
|
||||
/* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields
|
||||
* (if we have a lot of pointers in one hash bucket, or rehashing),
|
||||
* check if we reached the time limit.
|
||||
* But regardless, don't start a new BIG key in this loop, this is because the
|
||||
* next key can be a list, and scanLaterList must be done in once cycle */
|
||||
if (!cursor || (++iterations > 16 ||
|
||||
* check if we reached the time limit. */
|
||||
if (quit || (++iterations > 16 ||
|
||||
server.stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
server.stat_active_defrag_scanned - prev_scanned > 64)) {
|
||||
if (!cursor || ustime() > endtime) {
|
||||
if (quit || ustime() > endtime) {
|
||||
if(key_defragged != server.stat_active_defrag_hits)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
else
|
||||
|
32
src/dict.c
32
src/dict.c
@ -327,7 +327,7 @@ int dictReplace(dict *d, void *key, void *val)
|
||||
dictEntry *entry, *existing, auxentry;
|
||||
|
||||
/* Try to add the element. If the key
|
||||
* does not exists dictAdd will suceed. */
|
||||
* does not exists dictAdd will succeed. */
|
||||
entry = dictAddRaw(d,key,&existing);
|
||||
if (entry) {
|
||||
dictSetVal(d, entry, val);
|
||||
@ -705,8 +705,10 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
* table, there will be no elements in both tables up to
|
||||
* the current rehashing index, so we jump if possible.
|
||||
* (this happens when going from big to small table). */
|
||||
if (i >= d->ht[1].size) i = d->rehashidx;
|
||||
continue;
|
||||
if (i >= d->ht[1].size)
|
||||
i = d->rehashidx;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
if (i >= d->ht[j].size) continue; /* Out of range for this table. */
|
||||
dictEntry *he = d->ht[j].table[i];
|
||||
@ -737,6 +739,30 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
return stored;
|
||||
}
|
||||
|
||||
/* This is like dictGetRandomKey() from the POV of the API, but will do more
|
||||
* work to ensure a better distribution of the returned element.
|
||||
*
|
||||
* This function improves the distribution because the dictGetRandomKey()
|
||||
* problem is that it selects a random bucket, then it selects a random
|
||||
* element from the chain in the bucket. However elements being in different
|
||||
* chain lengths will have different probabilities of being reported. With
|
||||
* this function instead what we do is to consider a "linear" range of the table
|
||||
* that may be constituted of N buckets with chains of different lengths
|
||||
* appearing one after the other. Then we report a random element in the range.
|
||||
* In this way we smooth away the problem of different chain lenghts. */
|
||||
#define GETFAIR_NUM_ENTRIES 15
|
||||
dictEntry *dictGetFairRandomKey(dict *d) {
|
||||
dictEntry *entries[GETFAIR_NUM_ENTRIES];
|
||||
unsigned int count = dictGetSomeKeys(d,entries,GETFAIR_NUM_ENTRIES);
|
||||
/* Note that dictGetSomeKeys() may return zero elements in an unlucky
|
||||
* run() even if there are actually elements inside the hash table. So
|
||||
* when we get zero, we call the true dictGetRandomKey() that will always
|
||||
* yeld the element if the hash table has at least one. */
|
||||
if (count == 0) return dictGetRandomKey(d);
|
||||
unsigned int idx = rand() % count;
|
||||
return entries[idx];
|
||||
}
|
||||
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
|
||||
static unsigned long rev(unsigned long v) {
|
||||
|
@ -166,6 +166,7 @@ dictIterator *dictGetSafeIterator(dict *d);
|
||||
dictEntry *dictNext(dictIterator *iter);
|
||||
void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
dictEntry *dictGetFairRandomKey(dict *d);
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
uint64_t dictGenHashFunction(const void *key, int len);
|
||||
|
@ -43,7 +43,7 @@ uint16_t intrev16(uint16_t v);
|
||||
uint32_t intrev32(uint32_t v);
|
||||
uint64_t intrev64(uint64_t v);
|
||||
|
||||
/* variants of the function doing the actual convertion only if the target
|
||||
/* variants of the function doing the actual conversion only if the target
|
||||
* host is big endian */
|
||||
#if (BYTE_ORDER == LITTLE_ENDIAN)
|
||||
#define memrev16ifbe(p) ((void)(0))
|
||||
|
19
src/evict.c
19
src/evict.c
@ -78,7 +78,7 @@ unsigned int getLRUClock(void) {
|
||||
unsigned int LRU_CLOCK(void) {
|
||||
unsigned int lruclock;
|
||||
if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
|
||||
atomicGet(server.lruclock,lruclock);
|
||||
lruclock = server.lruclock;
|
||||
} else {
|
||||
lruclock = getLRUClock();
|
||||
}
|
||||
@ -364,7 +364,7 @@ size_t freeMemoryGetNotCountedMemory(void) {
|
||||
}
|
||||
}
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
overhead += sdslen(server.aof_buf)+aofRewriteBufferSize();
|
||||
overhead += sdsalloc(server.aof_buf)+aofRewriteBufferSize();
|
||||
}
|
||||
return overhead;
|
||||
}
|
||||
@ -444,6 +444,10 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
* Otehrwise if we are over the memory limit, but not enough memory
|
||||
* was freed to return back under the limit, the function returns C_ERR. */
|
||||
int freeMemoryIfNeeded(void) {
|
||||
/* By default replicas should ignore maxmemory
|
||||
* and just be masters exact copies. */
|
||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||
|
||||
size_t mem_reported, mem_tofree, mem_freed;
|
||||
mstime_t latency, eviction_latency;
|
||||
long long delta;
|
||||
@ -618,3 +622,14 @@ cant_free:
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
|
||||
* function if right now there are the conditions to do so safely:
|
||||
*
|
||||
* - There must be no script in timeout condition.
|
||||
* - Nor we are loading data right now.
|
||||
*
|
||||
*/
|
||||
int freeMemoryIfNeededAndSafe(void) {
|
||||
if (server.lua_timedout || server.loading) return C_OK;
|
||||
return freeMemoryIfNeeded();
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
trackingInvalidateKey(keyobj);
|
||||
decrRefCount(keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
return 1;
|
||||
@ -112,7 +113,7 @@ void activeExpireCycle(int type) {
|
||||
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
||||
/* Don't start a fast cycle if the previous cycle did not exit
|
||||
* for time limt. Also don't repeat a fast cycle for the same period
|
||||
* for time limit. Also don't repeat a fast cycle for the same period
|
||||
* as the fast cycle total duration itself. */
|
||||
if (!timelimit_exit) return;
|
||||
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
|
||||
|
36
src/geo.c
36
src/geo.c
@ -145,7 +145,7 @@ double extractUnitOrReply(client *c, robj *unit) {
|
||||
/* Input Argument Helper.
|
||||
* Extract the dinstance from the specified two arguments starting at 'argv'
|
||||
* that shouldbe in the form: <number> <unit> and return the dinstance in the
|
||||
* specified unit on success. *conversino is populated with the coefficient
|
||||
* specified unit on success. *conversions is populated with the coefficient
|
||||
* to use in order to convert meters to the unit.
|
||||
*
|
||||
* On error a value less than zero is returned. */
|
||||
@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptymultibulk)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL ||
|
||||
checkType(c, zobj, OBJ_ZSET)) {
|
||||
return;
|
||||
}
|
||||
@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
/* If no matching results, the user gets an empty reply. */
|
||||
if (ga->used == 0 && storekey == NULL) {
|
||||
addReply(c, shared.emptymultibulk);
|
||||
addReplyNull(c);
|
||||
geoArrayFree(ga);
|
||||
return;
|
||||
}
|
||||
@ -597,11 +597,11 @@ void georadiusGeneric(client *c, int flags) {
|
||||
if (withhash)
|
||||
option_length++;
|
||||
|
||||
/* The multibulk len we send is exactly result_length. The result is
|
||||
/* The array len we send is exactly result_length. The result is
|
||||
* either all strings of just zset members *or* a nested multi-bulk
|
||||
* reply containing the zset member string _and_ all the additional
|
||||
* options the user enabled for this request. */
|
||||
addReplyMultiBulkLen(c, returned_items);
|
||||
addReplyArrayLen(c, returned_items);
|
||||
|
||||
/* Finally send results back to the caller */
|
||||
int i;
|
||||
@ -613,7 +613,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
* as a nested multi-bulk. Add 1 to account for result value
|
||||
* itself. */
|
||||
if (option_length)
|
||||
addReplyMultiBulkLen(c, option_length + 1);
|
||||
addReplyArrayLen(c, option_length + 1);
|
||||
|
||||
addReplyBulkSds(c,gp->member);
|
||||
gp->member = NULL;
|
||||
@ -625,7 +625,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
addReplyLongLong(c, gp->score);
|
||||
|
||||
if (withcoords) {
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyHumanLongDouble(c, gp->longitude);
|
||||
addReplyHumanLongDouble(c, gp->latitude);
|
||||
}
|
||||
@ -659,7 +659,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
setKey(c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += returned_items;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
@ -706,11 +706,11 @@ void geohashCommand(client *c) {
|
||||
|
||||
/* Geohash elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
} else {
|
||||
/* The internal format we use for geocoding is a bit different
|
||||
* than the standard, since we use as initial latitude range
|
||||
@ -721,7 +721,7 @@ void geohashCommand(client *c) {
|
||||
/* Decode... */
|
||||
double xy[2];
|
||||
if (!decodeGeohash(score,xy)) {
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -759,19 +759,19 @@ void geoposCommand(client *c) {
|
||||
|
||||
/* Report elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
} else {
|
||||
/* Decode... */
|
||||
double xy[2];
|
||||
if (!decodeGeohash(score,xy)) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
continue;
|
||||
}
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyHumanLongDouble(c,xy[0]);
|
||||
addReplyHumanLongDouble(c,xy[1]);
|
||||
}
|
||||
@ -797,7 +797,7 @@ void geodistCommand(client *c) {
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.nullbulk))
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp]))
|
||||
== NULL || checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Get the scores. We need both otherwise NULL is returned. */
|
||||
@ -805,13 +805,13 @@ void geodistCommand(client *c) {
|
||||
if (zsetScore(zobj, c->argv[2]->ptr, &score1) == C_ERR ||
|
||||
zsetScore(zobj, c->argv[3]->ptr, &score2) == C_ERR)
|
||||
{
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decode & compute the distance. */
|
||||
if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
else
|
||||
addReplyDoubleDistance(c,
|
||||
geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
|
||||
|
@ -127,8 +127,8 @@ int geohashEncode(const GeoHashRange *long_range, const GeoHashRange *lat_range,
|
||||
|
||||
/* Return an error when trying to index outside the supported
|
||||
* constraints. */
|
||||
if (longitude > 180 || longitude < -180 ||
|
||||
latitude > 85.05112878 || latitude < -85.05112878) return 0;
|
||||
if (longitude > GEO_LONG_MAX || longitude < GEO_LONG_MIN ||
|
||||
latitude > GEO_LAT_MAX || latitude < GEO_LAT_MIN) return 0;
|
||||
|
||||
hash->bits = 0;
|
||||
hash->step = step;
|
||||
|
97
src/gopher.c
Normal file
97
src/gopher.c
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
/* Emit an item in Gopher directory listing format:
|
||||
* <type><descr><TAB><selector><TAB><hostname><TAB><port>
|
||||
* If descr or selector are NULL, then the "(NULL)" string is used instead. */
|
||||
void addReplyGopherItem(client *c, const char *type, const char *descr,
|
||||
const char *selector, const char *hostname, int port)
|
||||
{
|
||||
sds item = sdscatfmt(sdsempty(),"%s%s\t%s\t%s\t%i\r\n",
|
||||
type, descr,
|
||||
selector ? selector : "(NULL)",
|
||||
hostname ? hostname : "(NULL)",
|
||||
port);
|
||||
addReplyProto(c,item,sdslen(item));
|
||||
sdsfree(item);
|
||||
}
|
||||
|
||||
/* This is called by processInputBuffer() when an inline request is processed
|
||||
* with Gopher mode enabled, and the request happens to have zero or just one
|
||||
* argument. In such case we get the relevant key and reply using the Gopher
|
||||
* protocol. */
|
||||
void processGopherRequest(client *c) {
|
||||
robj *keyname = c->argc == 0 ? createStringObject("/",1) : c->argv[0];
|
||||
robj *o = lookupKeyRead(c->db,keyname);
|
||||
|
||||
/* If there is no such key, return with a Gopher error. */
|
||||
if (o == NULL || o->type != OBJ_STRING) {
|
||||
char *errstr;
|
||||
if (o == NULL)
|
||||
errstr = "Error: no content at the specified key";
|
||||
else
|
||||
errstr = "Error: selected key type is invalid "
|
||||
"for Gopher output";
|
||||
addReplyGopherItem(c,"i",errstr,NULL,NULL,0);
|
||||
addReplyGopherItem(c,"i","Redis Gopher server",NULL,NULL,0);
|
||||
} else {
|
||||
addReply(c,o);
|
||||
}
|
||||
|
||||
/* Cleanup, also make sure to emit the final ".CRLF" line. Note that
|
||||
* the connection will be closed immediately after this because the client
|
||||
* will be flagged with CLIENT_CLOSE_AFTER_REPLY, in accordance with the
|
||||
* Gopher protocol. */
|
||||
if (c->argc == 0) decrRefCount(keyname);
|
||||
|
||||
/* Note that in theory we should terminate the Gopher request with
|
||||
* ".<CR><LF>" (called Lastline in the RFC) like that:
|
||||
*
|
||||
* addReplyProto(c,".\r\n",3);
|
||||
*
|
||||
* However after examining the current clients landscape, it's probably
|
||||
* going to do more harm than good for several reasons:
|
||||
*
|
||||
* 1. Clients should not have any issue with missing .<CR><LF> as for
|
||||
* specification, and in the real world indeed certain servers
|
||||
* implementations never used to send the terminator.
|
||||
*
|
||||
* 2. Redis does not know if it's serving a text file or a binary file:
|
||||
* at the same time clients will not remove the ".<CR><LF>" bytes at
|
||||
* tne end when downloading a binary file from the server, so adding
|
||||
* the "Lastline" terminator without knowing the content is just
|
||||
* dangerous.
|
||||
*
|
||||
* 3. The utility gopher2redis.rb that we provide for Redis, and any
|
||||
* other similar tool you may use as Gopher authoring system for
|
||||
* Redis, can just add the "Lastline" when needed.
|
||||
*/
|
||||
}
|
66
src/help.h
66
src/help.h
@ -98,6 +98,11 @@ struct commandHelp {
|
||||
"Get the current connection name",
|
||||
9,
|
||||
"2.6.9" },
|
||||
{ "CLIENT ID",
|
||||
"-",
|
||||
"Returns the client ID for the current connection",
|
||||
9,
|
||||
"5.0.0" },
|
||||
{ "CLIENT KILL",
|
||||
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]",
|
||||
"Kill the connection of a client",
|
||||
@ -123,6 +128,11 @@ struct commandHelp {
|
||||
"Set the current connection name",
|
||||
9,
|
||||
"2.6.9" },
|
||||
{ "CLIENT UNBLOCK",
|
||||
"client-id [TIMEOUT|ERROR]",
|
||||
"Unblock a client blocked in a blocking command from a different connection",
|
||||
9,
|
||||
"5.0.0" },
|
||||
{ "CLUSTER ADDSLOTS",
|
||||
"slot [slot ...]",
|
||||
"Assign new hash slots to receiving node",
|
||||
@ -145,7 +155,7 @@ struct commandHelp {
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FAILOVER",
|
||||
"[FORCE|TAKEOVER]",
|
||||
"Forces a slave to perform a manual failover of its master.",
|
||||
"Forces a replica to perform a manual failover of its master.",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FORGET",
|
||||
@ -178,9 +188,14 @@ struct commandHelp {
|
||||
"Get Cluster config for the node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER REPLICAS",
|
||||
"node-id",
|
||||
"List replica nodes of the specified master node",
|
||||
12,
|
||||
"5.0.0" },
|
||||
{ "CLUSTER REPLICATE",
|
||||
"node-id",
|
||||
"Reconfigure a node as a slave of the specified master node",
|
||||
"Reconfigure a node as a replica of the specified master node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER RESET",
|
||||
@ -205,7 +220,7 @@ struct commandHelp {
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SLAVES",
|
||||
"node-id",
|
||||
"List slave nodes of the specified master node",
|
||||
"List replica nodes of the specified master node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SLOTS",
|
||||
@ -690,12 +705,12 @@ struct commandHelp {
|
||||
"1.0.0" },
|
||||
{ "READONLY",
|
||||
"-",
|
||||
"Enables read queries for a connection to a cluster slave node",
|
||||
"Enables read queries for a connection to a cluster replica node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "READWRITE",
|
||||
"-",
|
||||
"Disables read queries for a connection to a cluster slave node",
|
||||
"Disables read queries for a connection to a cluster replica node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "RENAME",
|
||||
@ -708,6 +723,11 @@ struct commandHelp {
|
||||
"Rename a key, only if the new key does not exist",
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "REPLICAOF",
|
||||
"host port",
|
||||
"Make the server a replica of another instance, or promote it as master.",
|
||||
9,
|
||||
"5.0.0" },
|
||||
{ "RESTORE",
|
||||
"key ttl serialized-value [REPLACE]",
|
||||
"Create a key using the provided serialized value, previously obtained using DUMP.",
|
||||
@ -845,7 +865,7 @@ struct commandHelp {
|
||||
"1.0.0" },
|
||||
{ "SLAVEOF",
|
||||
"host port",
|
||||
"Make the server a slave of another instance, or promote it as master",
|
||||
"Make the server a replica of another instance, or promote it as master. Deprecated starting with Redis 5. Use REPLICAOF instead.",
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "SLOWLOG",
|
||||
@ -954,7 +974,7 @@ struct commandHelp {
|
||||
7,
|
||||
"2.2.0" },
|
||||
{ "WAIT",
|
||||
"numslaves timeout",
|
||||
"numreplicas timeout",
|
||||
"Wait for the synchronous replication of all the write commands sent in the context of the current connection",
|
||||
0,
|
||||
"3.0.0" },
|
||||
@ -963,11 +983,36 @@ struct commandHelp {
|
||||
"Watch the given keys to determine execution of the MULTI/EXEC block",
|
||||
7,
|
||||
"2.2.0" },
|
||||
{ "XACK",
|
||||
"key group ID [ID ...]",
|
||||
"Marks a pending message as correctly processed, effectively removing it from the pending entries list of the consumer group. Return value of the command is the number of messages successfully acknowledged, that is, the IDs we were actually able to resolve in the PEL.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XADD",
|
||||
"key ID field string [field string ...]",
|
||||
"Appends a new entry to a stream",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XCLAIM",
|
||||
"key group consumer min-idle-time ID [ID ...] [IDLE ms] [TIME ms-unix-time] [RETRYCOUNT count] [force] [justid]",
|
||||
"Changes (or acquires) ownership of a message in a consumer group, as if the message was delivered to the specified consumer.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XDEL",
|
||||
"key ID [ID ...]",
|
||||
"Removes the specified entries from the stream. Returns the number of items actually deleted, that may be different from the number of IDs passed in case certain IDs do not exist.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XGROUP",
|
||||
"[CREATE key groupname id-or-$] [SETID key id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
|
||||
"Create, destroy, and manage consumer groups.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XINFO",
|
||||
"[CONSUMERS key groupname] [GROUPS key] [STREAM key] [HELP]",
|
||||
"Get information on streams and consumer groups",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XLEN",
|
||||
"key",
|
||||
"Return the number of entires in a stream",
|
||||
@ -975,7 +1020,7 @@ struct commandHelp {
|
||||
"5.0.0" },
|
||||
{ "XPENDING",
|
||||
"key group [start end count] [consumer]",
|
||||
"Return information and entries from a stream conusmer group pending entries list, that are messages fetched but never acknowledged.",
|
||||
"Return information and entries from a stream consumer group pending entries list, that are messages fetched but never acknowledged.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XRANGE",
|
||||
@ -998,6 +1043,11 @@ struct commandHelp {
|
||||
"Return a range of elements in a stream, with IDs matching the specified IDs interval, in reverse order (from greater to smaller IDs) compared to XRANGE",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XTRIM",
|
||||
"key MAXLEN [~] count",
|
||||
"Trims the stream to (approximately if '~' is passed) a certain size",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "ZADD",
|
||||
"key [NX|XX] [CH] [INCR] score member [score member ...]",
|
||||
"Add one or more members to a sorted set, or update its score if it already exists",
|
||||
|
@ -614,6 +614,7 @@ int hllSparseToDense(robj *o) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
|
||||
idx++;
|
||||
@ -673,7 +674,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
end = p + sdslen(o->ptr) - HLL_HDR_SIZE;
|
||||
|
||||
first = 0;
|
||||
prev = NULL; /* Points to previos opcode at the end of the loop. */
|
||||
prev = NULL; /* Points to previous opcode at the end of the loop. */
|
||||
next = NULL; /* Points to the next opcode at the end of the loop. */
|
||||
span = 0;
|
||||
while(p < end) {
|
||||
@ -699,7 +700,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
p += oplen;
|
||||
first += span;
|
||||
}
|
||||
if (span == 0) return -1; /* Invalid format. */
|
||||
if (span == 0 || p >= end) return -1; /* Invalid format. */
|
||||
|
||||
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
|
||||
if (next >= end) next = NULL;
|
||||
@ -764,7 +765,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
* and is either currently represented by a VAL opcode with len > 1,
|
||||
* by a ZERO opcode with len > 1, or by an XZERO opcode.
|
||||
*
|
||||
* In those cases the original opcode must be split into muliple
|
||||
* In those cases the original opcode must be split into multiple
|
||||
* opcodes. The worst case is an XZERO split in the middle resuling into
|
||||
* XZERO - VAL - XZERO, so the resulting sequence max length is
|
||||
* 5 bytes.
|
||||
@ -887,7 +888,7 @@ promote: /* Promote to dense representation. */
|
||||
*
|
||||
* Note that this in turn means that PFADD will make sure the command
|
||||
* is propagated to slaves / AOF, so if there is a sparse -> dense
|
||||
* convertion, it will be performed in all the slaves as well. */
|
||||
* conversion, it will be performed in all the slaves as well. */
|
||||
int dense_retval = hllDenseSet(hdr->registers,index,count);
|
||||
serverAssert(dense_retval == 1);
|
||||
return dense_retval;
|
||||
@ -1013,7 +1014,12 @@ uint64_t hllCount(struct hllhdr *hdr, int *invalid) {
|
||||
double m = HLL_REGISTERS;
|
||||
double E;
|
||||
int j;
|
||||
int reghisto[HLL_Q+2] = {0};
|
||||
/* Note that reghisto size could be just HLL_Q+2, becuase HLL_Q+1 is
|
||||
* the maximum frequency of the "000...1" sequence the hash function is
|
||||
* able to return. However it is slow to check for sanity of the
|
||||
* input: instead we history array at a safe size: overflows will
|
||||
* just write data to wrong, but correctly allocated, places. */
|
||||
int reghisto[64] = {0};
|
||||
|
||||
/* Compute register histogram */
|
||||
if (hdr->encoding == HLL_DENSE) {
|
||||
@ -1088,6 +1094,7 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
if (regval > max[i]) max[i] = regval;
|
||||
i++;
|
||||
@ -1512,7 +1519,7 @@ void pfdebugCommand(client *c) {
|
||||
}
|
||||
|
||||
hdr = o->ptr;
|
||||
addReplyMultiBulkLen(c,HLL_REGISTERS);
|
||||
addReplyArrayLen(c,HLL_REGISTERS);
|
||||
for (j = 0; j < HLL_REGISTERS; j++) {
|
||||
uint8_t val;
|
||||
|
||||
|
@ -123,7 +123,7 @@ static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
|
||||
} else {
|
||||
/* Check for the case where we know we cannot find the value,
|
||||
* but do know the insert position. */
|
||||
if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
|
||||
if (value > _intsetGet(is,max)) {
|
||||
if (pos) *pos = intrev32ifbe(is->length);
|
||||
return 0;
|
||||
} else if (value < _intsetGet(is,0)) {
|
||||
|
@ -152,7 +152,7 @@ int latencyResetEvent(char *event_to_reset) {
|
||||
|
||||
/* ------------------------ Latency reporting (doctor) ---------------------- */
|
||||
|
||||
/* Analyze the samples avaialble for a given event and return a structure
|
||||
/* Analyze the samples available for a given event and return a structure
|
||||
* populate with different metrics, average, MAD, min, max, and so forth.
|
||||
* Check latency.h definition of struct latenctStat for more info.
|
||||
* If the specified event has no elements the structure is populate with
|
||||
@ -294,7 +294,7 @@ sds createLatencyReport(void) {
|
||||
|
||||
/* Potentially commands. */
|
||||
if (!strcasecmp(event,"command")) {
|
||||
if (server.slowlog_log_slower_than == 0) {
|
||||
if (server.slowlog_log_slower_than < 0) {
|
||||
advise_slowlog_enabled = 1;
|
||||
advices++;
|
||||
} else if (server.slowlog_log_slower_than/1000 >
|
||||
@ -476,19 +476,19 @@ sds createLatencyReport(void) {
|
||||
/* latencyCommand() helper to produce a time-delay reply for all the samples
|
||||
* in memory for the specified time series. */
|
||||
void latencyCommandReplyWithSamples(client *c, struct latencyTimeSeries *ts) {
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int samples = 0, j;
|
||||
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int i = (ts->idx + j) % LATENCY_TS_LEN;
|
||||
|
||||
if (ts->samples[i].time == 0) continue;
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c,ts->samples[i].time);
|
||||
addReplyLongLong(c,ts->samples[i].latency);
|
||||
samples++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,samples);
|
||||
setDeferredArrayLen(c,replylen,samples);
|
||||
}
|
||||
|
||||
/* latencyCommand() helper to produce the reply for the LATEST subcommand,
|
||||
@ -497,14 +497,14 @@ void latencyCommandReplyWithLatestEvents(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyMultiBulkLen(c,dictSize(server.latency_events));
|
||||
addReplyArrayLen(c,dictSize(server.latency_events));
|
||||
di = dictGetIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
int last = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyArrayLen(c,4);
|
||||
addReplyBulkCString(c,event);
|
||||
addReplyLongLong(c,ts->samples[last].time);
|
||||
addReplyLongLong(c,ts->samples[last].latency);
|
||||
@ -560,19 +560,30 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
|
||||
|
||||
/* LATENCY command implementations.
|
||||
*
|
||||
* LATENCY SAMPLES: return time-latency samples for the specified event.
|
||||
* LATENCY HISTORY: return time-latency samples for the specified event.
|
||||
* LATENCY LATEST: return the latest latency for all the events classes.
|
||||
* LATENCY DOCTOR: returns an human readable analysis of instance latency.
|
||||
* LATENCY DOCTOR: returns a human readable analysis of instance latency.
|
||||
* LATENCY GRAPH: provide an ASCII graph of the latency of the specified event.
|
||||
* LATENCY RESET: reset data of a specified event or all the data if no event provided.
|
||||
*/
|
||||
void latencyCommand(client *c) {
|
||||
const char *help[] = {
|
||||
"DOCTOR -- Returns a human readable latency analysis report.",
|
||||
"GRAPH <event> -- Returns an ASCII latency graph for the event class.",
|
||||
"HISTORY <event> -- Returns time-latency samples for the event class.",
|
||||
"LATEST -- Returns the latest latency samples for all events.",
|
||||
"RESET [event ...] -- Resets latency data of one or more event classes.",
|
||||
" (default: reset all data for all event classes)",
|
||||
"HELP -- Prints this help.",
|
||||
NULL
|
||||
};
|
||||
struct latencyTimeSeries *ts;
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
|
||||
/* LATENCY HISTORY <event> */
|
||||
ts = dictFetchValue(server.latency_events,c->argv[2]->ptr);
|
||||
if (ts == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
addReplyArrayLen(c,0);
|
||||
} else {
|
||||
latencyCommandReplyWithSamples(c,ts);
|
||||
}
|
||||
@ -610,8 +621,10 @@ void latencyCommand(client *c) {
|
||||
resets += latencyResetEvent(c->argv[j]->ptr);
|
||||
addReplyLongLong(c,resets);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc >= 2) {
|
||||
addReplyHelp(c, help);
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
return;
|
||||
|
||||
|
@ -23,10 +23,10 @@ size_t lazyfreeGetPendingObjectsCount(void) {
|
||||
* the function just returns the number of elements the object is composed of.
|
||||
*
|
||||
* Objects composed of single allocations are always reported as having a
|
||||
* single item even if they are actaully logical composed of multiple
|
||||
* single item even if they are actually logical composed of multiple
|
||||
* elements.
|
||||
*
|
||||
* For lists the funciton returns the number of elements in the quicklist
|
||||
* For lists the function returns the number of elements in the quicklist
|
||||
* representing the list. */
|
||||
size_t lazyfreeGetFreeEffort(robj *obj) {
|
||||
if (obj->type == OBJ_LIST) {
|
||||
@ -90,6 +90,17 @@ int dbAsyncDelete(redisDb *db, robj *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) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,o,NULL,NULL);
|
||||
} else {
|
||||
decrRefCount(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. */
|
||||
|
@ -291,7 +291,7 @@ int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, ui
|
||||
/* Store a reverse-encoded variable length field, representing the length
|
||||
* of the previous element of size 'l', in the target buffer 'buf'.
|
||||
* The function returns the number of bytes used to encode it, from
|
||||
* 1 to 5. If 'buf' is NULL the funciton just returns the number of bytes
|
||||
* 1 to 5. If 'buf' is NULL the function just returns the number of bytes
|
||||
* needed in order to encode the backlen. */
|
||||
unsigned long lpEncodeBacklen(unsigned char *buf, uint64_t l) {
|
||||
if (l <= 127) {
|
||||
@ -568,7 +568,7 @@ unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert, delete or replace the specified element 'ele' of lenght 'len' at
|
||||
/* Insert, delete or replace the specified element 'ele' of length 'len' at
|
||||
* the specified position 'p', with 'p' being a listpack element pointer
|
||||
* obtained with lpFirst(), lpLast(), lpIndex(), lpNext(), lpPrev() or
|
||||
* lpSeek().
|
||||
@ -707,10 +707,30 @@ unsigned char *lpInsert(unsigned char *lp, unsigned char *ele, uint32_t size, un
|
||||
}
|
||||
}
|
||||
lpSetTotalBytes(lp,new_listpack_bytes);
|
||||
|
||||
#if 0
|
||||
/* This code path is normally disabled: what it does is to force listpack
|
||||
* to return *always* a new pointer after performing some modification to
|
||||
* the listpack, even if the previous allocation was enough. This is useful
|
||||
* in order to spot bugs in code using listpacks: by doing so we can find
|
||||
* if the caller forgets to set the new pointer where the listpack reference
|
||||
* is stored, after an update. */
|
||||
unsigned char *oldlp = lp;
|
||||
lp = lp_malloc(new_listpack_bytes);
|
||||
memcpy(lp,oldlp,new_listpack_bytes);
|
||||
if (newp) {
|
||||
unsigned long offset = (*newp)-oldlp;
|
||||
*newp = lp + offset;
|
||||
}
|
||||
/* Make sure the old allocation contains garbage. */
|
||||
memset(oldlp,'A',new_listpack_bytes);
|
||||
lp_free(oldlp);
|
||||
#endif
|
||||
|
||||
return lp;
|
||||
}
|
||||
|
||||
/* Append the specified element 'ele' of lenght 'len' at the end of the
|
||||
/* Append the specified element 'ele' of length 'len' at the end of the
|
||||
* listpack. It is implemented in terms of lpInsert(), so the return value is
|
||||
* the same as lpInsert(). */
|
||||
unsigned char *lpAppend(unsigned char *lp, unsigned char *ele, uint32_t size) {
|
||||
|
123
src/localtime.c
Normal file
123
src/localtime.c
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 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 <time.h>
|
||||
|
||||
/* This is a safe version of localtime() which contains no locks and is
|
||||
* fork() friendly. Even the _r version of localtime() cannot be used safely
|
||||
* in Redis. Another thread may be calling localtime() while the main thread
|
||||
* forks(). Later when the child process calls localtime() again, for instance
|
||||
* in order to log something to the Redis log, it may deadlock: in the copy
|
||||
* of the address space of the forked process the lock will never be released.
|
||||
*
|
||||
* This function takes the timezone 'tz' as argument, and the 'dst' flag is
|
||||
* used to check if daylight saving time is currently in effect. The caller
|
||||
* of this function should obtain such information calling tzset() ASAP in the
|
||||
* main() function to obtain the timezone offset from the 'timezone' global
|
||||
* variable. To obtain the daylight information, if it is currently active or not,
|
||||
* one trick is to call localtime() in main() ASAP as well, and get the
|
||||
* information from the tm_isdst field of the tm structure. However the daylight
|
||||
* time may switch in the future for long running processes, so this information
|
||||
* should be refreshed at safe times.
|
||||
*
|
||||
* Note that this function does not work for dates < 1/1/1970, it is solely
|
||||
* designed to work with what time(NULL) may return, and to support Redis
|
||||
* logging of the dates, it's not really a complete implementation. */
|
||||
static int is_leap_year(time_t year) {
|
||||
if (year % 4) return 0; /* A year not divisible by 4 is not leap. */
|
||||
else if (year % 100) return 1; /* If div by 4 and not 100 is surely leap. */
|
||||
else if (year % 400) return 0; /* If div by 100 *and* 400 is not leap. */
|
||||
else return 1; /* If div by 100 and not by 400 is leap. */
|
||||
}
|
||||
|
||||
void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) {
|
||||
const time_t secs_min = 60;
|
||||
const time_t secs_hour = 3600;
|
||||
const time_t secs_day = 3600*24;
|
||||
|
||||
t -= tz; /* Adjust for timezone. */
|
||||
t += 3600*dst; /* Adjust for daylight time. */
|
||||
time_t days = t / secs_day; /* Days passed since epoch. */
|
||||
time_t seconds = t % secs_day; /* Remaining seconds. */
|
||||
|
||||
tmp->tm_isdst = dst;
|
||||
tmp->tm_hour = seconds / secs_hour;
|
||||
tmp->tm_min = (seconds % secs_hour) / secs_min;
|
||||
tmp->tm_sec = (seconds % secs_hour) % secs_min;
|
||||
|
||||
/* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure
|
||||
* where sunday = 0, so to calculate the day of the week we have to add 4
|
||||
* and take the modulo by 7. */
|
||||
tmp->tm_wday = (days+4)%7;
|
||||
|
||||
/* Calculate the current year. */
|
||||
tmp->tm_year = 1970;
|
||||
while(1) {
|
||||
/* Leap years have one day more. */
|
||||
time_t days_this_year = 365 + is_leap_year(tmp->tm_year);
|
||||
if (days_this_year > days) break;
|
||||
days -= days_this_year;
|
||||
tmp->tm_year++;
|
||||
}
|
||||
tmp->tm_yday = days; /* Number of day of the current year. */
|
||||
|
||||
/* We need to calculate in which month and day of the month we are. To do
|
||||
* so we need to skip days according to how many days there are in each
|
||||
* month, and adjust for the leap year that has one more day in February. */
|
||||
int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
mdays[1] += is_leap_year(tmp->tm_year);
|
||||
|
||||
tmp->tm_mon = 0;
|
||||
while(days >= mdays[tmp->tm_mon]) {
|
||||
days -= mdays[tmp->tm_mon];
|
||||
tmp->tm_mon++;
|
||||
}
|
||||
|
||||
tmp->tm_mday = days+1; /* Add 1 since our 'days' is zero-based. */
|
||||
tmp->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */
|
||||
}
|
||||
|
||||
#ifdef LOCALTIME_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
/* Obtain timezone and daylight info. */
|
||||
tzset(); /* Now 'timezome' global is populated. */
|
||||
time_t t = time(NULL);
|
||||
struct tm *aux = localtime(&t);
|
||||
int daylight_active = aux->tm_isdst;
|
||||
|
||||
struct tm tm;
|
||||
char buf[1024];
|
||||
|
||||
nolocks_localtime(&tm,t,timezone,daylight_active);
|
||||
strftime(buf,sizeof(buf),"%d %b %H:%M:%S",&tm);
|
||||
printf("[timezone: %d, dl: %d] %s\n", (int)timezone, (int)daylight_active, buf);
|
||||
}
|
||||
#endif
|
56
src/lolwut.c
Normal file
56
src/lolwut.c
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 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.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This file implements the LOLWUT command. The command should do something
|
||||
* fun and interesting, and should be replaced by a new implementation at
|
||||
* each new version of Redis.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
void lolwut5Command(client *c);
|
||||
|
||||
/* The default target for LOLWUT if no matching version was found.
|
||||
* This is what unstable versions of Redis will display. */
|
||||
void lolwutUnstableCommand(client *c) {
|
||||
sds rendered = sdsnew("Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyBulkSds(c,rendered);
|
||||
}
|
||||
|
||||
void lolwutCommand(client *c) {
|
||||
char *v = REDIS_VERSION;
|
||||
if ((v[0] == '5' && v[1] == '.') ||
|
||||
(v[0] == '4' && v[1] == '.' && v[2] == '9'))
|
||||
lolwut5Command(c);
|
||||
else
|
||||
lolwutUnstableCommand(c);
|
||||
}
|
282
src/lolwut5.c
Normal file
282
src/lolwut5.c
Normal file
@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 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.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This file implements the LOLWUT command. The command should do something
|
||||
* fun and interesting, and should be replaced by a new implementation at
|
||||
* each new version of Redis.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include <math.h>
|
||||
|
||||
/* This structure represents our canvas. Drawing functions will take a pointer
|
||||
* to a canvas to write to it. Later the canvas can be rendered to a string
|
||||
* suitable to be printed on the screen, using unicode Braille characters. */
|
||||
typedef struct lwCanvas {
|
||||
int width;
|
||||
int height;
|
||||
char *pixels;
|
||||
} lwCanvas;
|
||||
|
||||
/* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding
|
||||
* braille character. The byte should correspond to the pixels arranged as
|
||||
* follows, where 0 is the least significant bit, and 7 the most significant
|
||||
* bit:
|
||||
*
|
||||
* 0 3
|
||||
* 1 4
|
||||
* 2 5
|
||||
* 6 7
|
||||
*
|
||||
* The corresponding utf8 encoded character is set into the three bytes
|
||||
* pointed by 'output'.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
void lwTranslatePixelsGroup(int byte, char *output) {
|
||||
int code = 0x2800 + byte;
|
||||
/* Convert to unicode. This is in the U0800-UFFFF range, so we need to
|
||||
* emit it like this in three bytes:
|
||||
* 1110xxxx 10xxxxxx 10xxxxxx. */
|
||||
output[0] = 0xE0 | (code >> 12); /* 1110-xxxx */
|
||||
output[1] = 0x80 | ((code >> 6) & 0x3F); /* 10-xxxxxx */
|
||||
output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */
|
||||
}
|
||||
|
||||
/* Allocate and return a new canvas of the specified size. */
|
||||
lwCanvas *lwCreateCanvas(int width, int height) {
|
||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
canvas->pixels = zmalloc(width*height);
|
||||
memset(canvas->pixels,0,width*height);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/* Free the canvas created by lwCreateCanvas(). */
|
||||
void lwFreeCanvas(lwCanvas *canvas) {
|
||||
zfree(canvas->pixels);
|
||||
zfree(canvas);
|
||||
}
|
||||
|
||||
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
||||
* dot will be displyed, and 1 means dot will be displayed.
|
||||
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
||||
* out of the size of the canvas without issues. */
|
||||
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return;
|
||||
canvas->pixels[x+y*canvas->width] = color;
|
||||
}
|
||||
|
||||
/* Return the value of the specified pixel on the canvas. */
|
||||
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return 0;
|
||||
return canvas->pixels[x+y*canvas->width];
|
||||
}
|
||||
|
||||
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
||||
int dx = abs(x2-x1);
|
||||
int dy = abs(y2-y1);
|
||||
int sx = (x1 < x2) ? 1 : -1;
|
||||
int sy = (y1 < y2) ? 1 : -1;
|
||||
int err = dx-dy, e2;
|
||||
|
||||
while(1) {
|
||||
lwDrawPixel(canvas,x1,y1,color);
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
e2 = err*2;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw a square centered at the specified x,y coordinates, with the specified
|
||||
* rotation angle and size. In order to write a rotated square, we use the
|
||||
* trivial fact that the parametric equation:
|
||||
*
|
||||
* x = sin(k)
|
||||
* y = cos(k)
|
||||
*
|
||||
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
||||
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
||||
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
||||
* points of the square. In order to rotate the square, we just start with
|
||||
* k = PI/4 + rotation_angle, and we are done.
|
||||
*
|
||||
* Of course the vanilla equations above will describe the square inside a
|
||||
* circle of radius 1, so in order to draw larger squares we'll have to
|
||||
* multiply the obtained coordinates, and then translate them. However this
|
||||
* is much simpler than implementing the abstract concept of 2D shape and then
|
||||
* performing the rotation/translation transformation, so for LOLWUT it's
|
||||
* a good approach. */
|
||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle) {
|
||||
int px[4], py[4];
|
||||
|
||||
/* Adjust the desired size according to the fact that the square inscribed
|
||||
* into a circle of radius 1 has the side of length SQRT(2). This way
|
||||
* size becomes a simple multiplication factor we can use with our
|
||||
* coordinates to magnify them. */
|
||||
size /= 1.4142135623;
|
||||
size = round(size);
|
||||
|
||||
/* Compute the four points. */
|
||||
float k = M_PI/4 + angle;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
px[j] = round(sin(k) * size + x);
|
||||
py[j] = round(cos(k) * size + y);
|
||||
k += M_PI/2;
|
||||
}
|
||||
|
||||
/* Draw the square. */
|
||||
for (int j = 0; j < 4; j++)
|
||||
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],1);
|
||||
}
|
||||
|
||||
/* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece
|
||||
* generated by Georg Nees in the 60s. It explores the relationship between
|
||||
* caos and order.
|
||||
*
|
||||
* The function creates the canvas itself, depending on the columns available
|
||||
* in the output display and the number of squares per row and per column
|
||||
* requested by the caller. */
|
||||
lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_col) {
|
||||
/* Calculate the canvas size. */
|
||||
int canvas_width = console_cols*2;
|
||||
int padding = canvas_width > 4 ? 2 : 0;
|
||||
float square_side = (float)(canvas_width-padding*2) / squares_per_row;
|
||||
int canvas_height = square_side * squares_per_col + padding*2;
|
||||
lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height);
|
||||
|
||||
for (int y = 0; y < squares_per_col; y++) {
|
||||
for (int x = 0; x < squares_per_row; x++) {
|
||||
int sx = x * square_side + square_side/2 + padding;
|
||||
int sy = y * square_side + square_side/2 + padding;
|
||||
/* Rotate and translate randomly as we go down to lower
|
||||
* rows. */
|
||||
float angle = 0;
|
||||
if (y > 1) {
|
||||
float r1 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
float r2 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
float r3 = (float)rand() / RAND_MAX / squares_per_col * y;
|
||||
if (rand() % 2) r1 = -r1;
|
||||
if (rand() % 2) r2 = -r2;
|
||||
if (rand() % 2) r3 = -r3;
|
||||
angle = r1;
|
||||
sx += r2*square_side/3;
|
||||
sy += r3*square_side/3;
|
||||
}
|
||||
lwDrawSquare(canvas,sx,sy,square_side,angle);
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/* Converts the canvas to an SDS string representing the UTF8 characters to
|
||||
* print to the terminal in order to obtain a graphical representaiton of the
|
||||
* logical canvas. The actual returned string will require a terminal that is
|
||||
* width/2 large and height/4 tall in order to hold the whole image without
|
||||
* overflowing or scrolling, since each Barille character is 2x4. */
|
||||
sds lwRenderCanvas(lwCanvas *canvas) {
|
||||
sds text = sdsempty();
|
||||
for (int y = 0; y < canvas->height; y += 4) {
|
||||
for (int x = 0; x < canvas->width; x += 2) {
|
||||
/* We need to emit groups of 8 bits according to a specific
|
||||
* arrangement. See lwTranslatePixelsGroup() for more info. */
|
||||
int byte = 0;
|
||||
if (lwGetPixel(canvas,x,y)) byte |= (1<<0);
|
||||
if (lwGetPixel(canvas,x,y+1)) byte |= (1<<1);
|
||||
if (lwGetPixel(canvas,x,y+2)) byte |= (1<<2);
|
||||
if (lwGetPixel(canvas,x+1,y)) byte |= (1<<3);
|
||||
if (lwGetPixel(canvas,x+1,y+1)) byte |= (1<<4);
|
||||
if (lwGetPixel(canvas,x+1,y+2)) byte |= (1<<5);
|
||||
if (lwGetPixel(canvas,x,y+3)) byte |= (1<<6);
|
||||
if (lwGetPixel(canvas,x+1,y+3)) byte |= (1<<7);
|
||||
char unicode[3];
|
||||
lwTranslatePixelsGroup(byte,unicode);
|
||||
text = sdscatlen(text,unicode,3);
|
||||
}
|
||||
if (y != canvas->height-1) text = sdscatlen(text,"\n",1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/* The LOLWUT command:
|
||||
*
|
||||
* LOLWUT [terminal columns] [squares-per-row] [squares-per-col]
|
||||
*
|
||||
* By default the command uses 66 columns, 8 squares per row, 12 squares
|
||||
* per column.
|
||||
*/
|
||||
void lolwut5Command(client *c) {
|
||||
long cols = 66;
|
||||
long squares_per_row = 8;
|
||||
long squares_per_col = 12;
|
||||
|
||||
/* Parse the optional arguments if any. */
|
||||
if (c->argc > 1 &&
|
||||
getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc > 2 &&
|
||||
getLongFromObjectOrReply(c,c->argv[2],&squares_per_row,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc > 3 &&
|
||||
getLongFromObjectOrReply(c,c->argv[3],&squares_per_col,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
/* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
|
||||
* so we have maximum number of columns, rows, and output resulution. */
|
||||
if (cols < 1) cols = 1;
|
||||
if (cols > 1000) cols = 1000;
|
||||
if (squares_per_row < 1) squares_per_row = 1;
|
||||
if (squares_per_row > 200) squares_per_row = 200;
|
||||
if (squares_per_col < 1) squares_per_col = 1;
|
||||
if (squares_per_col > 200) squares_per_col = 200;
|
||||
|
||||
/* Generate some computer art and reply. */
|
||||
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
|
||||
sds rendered = lwRenderCanvas(canvas);
|
||||
rendered = sdscat(rendered,
|
||||
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyBulkSds(c,rendered);
|
||||
lwFreeCanvas(canvas);
|
||||
}
|
11
src/lzf_d.c
11
src/lzf_d.c
@ -52,6 +52,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && __GNUC__ >= 5
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||
#endif
|
||||
unsigned int
|
||||
lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
void *out_data, unsigned int out_len)
|
||||
@ -86,8 +90,6 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
#ifdef lzf_movsb
|
||||
lzf_movsb (op, ip, ctrl);
|
||||
#else
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||
switch (ctrl)
|
||||
{
|
||||
case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++;
|
||||
@ -99,7 +101,6 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++;
|
||||
case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
else /* back reference */
|
||||
@ -185,4 +186,6 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
|
||||
return op - (u8 *)out_data;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) && __GNUC__ >= 5
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
@ -2,6 +2,9 @@
|
||||
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
|
||||
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
||||
BUILD_ID=`uname -n`"-"`date +%s`
|
||||
if [ -n "$SOURCE_DATE_EPOCH" ]; then
|
||||
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u %s)
|
||||
fi
|
||||
test -f release.h || touch release.h
|
||||
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
||||
(cat release.h | grep DIRTY | grep $GIT_DIRTY) && exit 0 # Already up-to-date
|
||||
|
1027
src/module.c
1027
src/module.c
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
@ -43,6 +43,10 @@ hellotimer.xo: ../redismodule.h
|
||||
hellotimer.so: hellotimer.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
hellodict.xo: ../redismodule.h
|
||||
|
||||
hellodict.so: hellodict.xo
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
testmodule.so: testmodule.xo
|
||||
|
@ -1,5 +1,5 @@
|
||||
# gendoc.rb -- Converts the top-comments inside module.c to modules API
|
||||
# reference documentaiton in markdown format.
|
||||
# reference documentation in markdown format.
|
||||
|
||||
# Convert the C comment to markdown
|
||||
def markdown(s)
|
||||
|
@ -77,7 +77,7 @@ void *HelloBlock_ThreadMain(void *arg) {
|
||||
/* An example blocked client disconnection callback.
|
||||
*
|
||||
* Note that in the case of the HELLO.BLOCK command, the blocked client is now
|
||||
* owned by the thread calling sleep(). In this speciifc case, there is not
|
||||
* owned by the thread calling sleep(). In this specific case, there is not
|
||||
* much we can do, however normally we could instead implement a way to
|
||||
* signal the thread that the client disconnected, and sleep the specified
|
||||
* amount of seconds with a while loop calling sleep(1), so that once we
|
||||
|
@ -69,7 +69,7 @@ int ListCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
|
||||
RedisModule_ReplyWithLongLong(ctx,port);
|
||||
}
|
||||
RedisModule_FreeClusterNodesList(ids);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Callback for message MSGTYPE_PING */
|
||||
@ -77,6 +77,7 @@ void PingReceiver(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, cons
|
||||
RedisModule_Log(ctx,"notice","PING (type %d) RECEIVED from %.*s: '%.*s'",
|
||||
type,REDISMODULE_NODE_ID_LEN,sender_id,(int)len, payload);
|
||||
RedisModule_SendClusterMessage(ctx,NULL,MSGTYPE_PONG,(unsigned char*)"Ohi!",4);
|
||||
RedisModule_Call(ctx, "INCR", "c", "pings_received");
|
||||
}
|
||||
|
||||
/* Callback for message MSGTYPE_PONG. */
|
||||
@ -102,6 +103,15 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
ListCommand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
/* Disable Redis Cluster sharding and redirections. This way every node
|
||||
* will be able to access every possible key, regardless of the hash slot.
|
||||
* This way the PING message handler will be able to increment a specific
|
||||
* variable. Normally you do that in order for the distributed system
|
||||
* you create as a module to have total freedom in the keyspace
|
||||
* manipulation. */
|
||||
RedisModule_SetClusterFlags(ctx,REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION);
|
||||
|
||||
/* Register our handlers for different message types. */
|
||||
RedisModule_RegisterClusterMessageReceiver(ctx,MSGTYPE_PING,PingReceiver);
|
||||
RedisModule_RegisterClusterMessageReceiver(ctx,MSGTYPE_PONG,PongReceiver);
|
||||
return REDISMODULE_OK;
|
||||
|
132
src/modules/hellodict.c
Normal file
132
src/modules/hellodict.c
Normal file
@ -0,0 +1,132 @@
|
||||
/* Hellodict -- An example of modules dictionary API
|
||||
*
|
||||
* This module implements a volatile key-value store on top of the
|
||||
* dictionary exported by the Redis modules API.
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2018, 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.
|
||||
*/
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "../redismodule.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
static RedisModuleDict *Keyspace;
|
||||
|
||||
/* HELLODICT.SET <key> <value>
|
||||
*
|
||||
* Set the specified key to the specified value. */
|
||||
int cmd_SET(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||
RedisModule_DictSet(Keyspace,argv[1],argv[2]);
|
||||
/* We need to keep a reference to the value stored at the key, otherwise
|
||||
* it would be freed when this callback returns. */
|
||||
RedisModule_RetainString(NULL,argv[2]);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
/* HELLODICT.GET <key>
|
||||
*
|
||||
* Return the value of the specified key, or a null reply if the key
|
||||
* is not defined. */
|
||||
int cmd_GET(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
RedisModuleString *val = RedisModule_DictGet(Keyspace,argv[1],NULL);
|
||||
if (val == NULL) {
|
||||
return RedisModule_ReplyWithNull(ctx);
|
||||
} else {
|
||||
return RedisModule_ReplyWithString(ctx, val);
|
||||
}
|
||||
}
|
||||
|
||||
/* HELLODICT.KEYRANGE <startkey> <endkey> <count>
|
||||
*
|
||||
* Return a list of matching keys, lexicographically between startkey
|
||||
* and endkey. No more than 'count' items are emitted. */
|
||||
int cmd_KEYRANGE(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 4) return RedisModule_WrongArity(ctx);
|
||||
|
||||
/* Parse the count argument. */
|
||||
long long count;
|
||||
if (RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) {
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid count");
|
||||
}
|
||||
|
||||
/* Seek the iterator. */
|
||||
RedisModuleDictIter *iter = RedisModule_DictIteratorStart(
|
||||
Keyspace, ">=", argv[1]);
|
||||
|
||||
/* Reply with the matching items. */
|
||||
char *key;
|
||||
size_t keylen;
|
||||
long long replylen = 0; /* Keep track of the amitted array len. */
|
||||
RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
while((key = RedisModule_DictNextC(iter,&keylen,NULL)) != NULL) {
|
||||
if (replylen >= count) break;
|
||||
if (RedisModule_DictCompare(iter,"<=",argv[2]) == REDISMODULE_ERR)
|
||||
break;
|
||||
RedisModule_ReplyWithStringBuffer(ctx,key,keylen);
|
||||
replylen++;
|
||||
}
|
||||
RedisModule_ReplySetArrayLength(ctx,replylen);
|
||||
|
||||
/* Cleanup. */
|
||||
RedisModule_DictIteratorStop(iter);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* This function must be present on each Redis module. It is used in order to
|
||||
* register the commands into the Redis server. */
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"hellodict",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hellodict.set",
|
||||
cmd_SET,"write deny-oom",1,1,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hellodict.get",
|
||||
cmd_GET,"readonly",1,1,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hellodict.keyrange",
|
||||
cmd_KEYRANGE,"readonly",1,1,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
/* Create our global dictionray. Here we'll set our keys and values. */
|
||||
Keyspace = RedisModule_CreateDict(NULL);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Helloworld cluster -- A ping/pong cluster API example.
|
||||
/* Timer API example -- Register and handle timer events
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
@ -37,9 +37,6 @@
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MSGTYPE_PING 1
|
||||
#define MSGTYPE_PONG 2
|
||||
|
||||
/* Timer callback. */
|
||||
void timerHandler(RedisModuleCtx *ctx, void *data) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
|
@ -109,9 +109,9 @@ int TestStringPrintf(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc < 3) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
|
||||
"Got %d args. argv[1]: %s, argv[2]: %s",
|
||||
argc,
|
||||
RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
|
||||
"Got %d args. argv[1]: %s, argv[2]: %s",
|
||||
argc,
|
||||
RedisModule_StringPtrLen(argv[1], NULL),
|
||||
RedisModule_StringPtrLen(argv[2], NULL)
|
||||
);
|
||||
@ -133,7 +133,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
|
||||
RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "unlinked"), REDISMODULE_WRITE | REDISMODULE_READ);
|
||||
if (!k) return failTest(ctx, "Could not create key");
|
||||
|
||||
|
||||
if (REDISMODULE_ERR == RedisModule_StringSet(k, RedisModule_CreateStringPrintf(ctx, "Foobar"))) {
|
||||
return failTest(ctx, "Could not set string value");
|
||||
}
|
||||
@ -152,7 +152,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return failTest(ctx, "Could not verify key to be unlinked");
|
||||
}
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
|
||||
|
||||
}
|
||||
|
||||
int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
|
||||
@ -188,6 +188,10 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
|
||||
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
|
||||
|
||||
/* Miss some keys intentionally so we will get a "keymiss" notification. */
|
||||
RedisModule_Call(ctx, "GET", "c", "nosuchkey");
|
||||
RedisModule_Call(ctx, "SMEMBERS", "c", "nosuchkey");
|
||||
|
||||
size_t sz;
|
||||
const char *rep;
|
||||
RedisModuleCallReply *r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo");
|
||||
@ -225,6 +229,16 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
FAIL("Wrong reply for l");
|
||||
}
|
||||
|
||||
r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "nosuchkey");
|
||||
if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
|
||||
FAIL("Wrong or no reply for nosuchkey");
|
||||
} else {
|
||||
rep = RedisModule_CallReplyStringPtr(r, &sz);
|
||||
if (sz != 1 || *rep != '2') {
|
||||
FAIL("Got reply '%.*s'. expected '2'", sz, rep);
|
||||
}
|
||||
}
|
||||
|
||||
RedisModule_Call(ctx, "FLUSHDB", "");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
@ -423,7 +437,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx,"test.ctxflags",
|
||||
TestCtxFlags,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"test.unlink",
|
||||
TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
@ -435,7 +449,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
RedisModule_SubscribeToKeyspaceEvents(ctx,
|
||||
REDISMODULE_NOTIFY_HASH |
|
||||
REDISMODULE_NOTIFY_SET |
|
||||
REDISMODULE_NOTIFY_STRING,
|
||||
REDISMODULE_NOTIFY_STRING |
|
||||
REDISMODULE_NOTIFY_KEY_MISS,
|
||||
NotifyCallback);
|
||||
if (RedisModule_CreateCommand(ctx,"test.notify",
|
||||
TestNotifications,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
|
23
src/multi.c
23
src/multi.c
@ -35,6 +35,7 @@
|
||||
void initClientMultiState(client *c) {
|
||||
c->mstate.commands = NULL;
|
||||
c->mstate.count = 0;
|
||||
c->mstate.cmd_flags = 0;
|
||||
}
|
||||
|
||||
/* Release all the resources associated with MULTI/EXEC state */
|
||||
@ -67,6 +68,7 @@ void queueMultiCommand(client *c) {
|
||||
for (j = 0; j < c->argc; j++)
|
||||
incrRefCount(mc->argv[j]);
|
||||
c->mstate.count++;
|
||||
c->mstate.cmd_flags |= c->cmd->flags;
|
||||
}
|
||||
|
||||
void discardTransaction(client *c) {
|
||||
@ -132,7 +134,22 @@ void execCommand(client *c) {
|
||||
* in the second an EXECABORT error is returned. */
|
||||
if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
|
||||
addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
|
||||
shared.nullmultibulk);
|
||||
shared.nullarray[c->resp]);
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* 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). */
|
||||
if (!server.loading && server.masterhost && server.repl_slave_ro &&
|
||||
!(c->flags & CLIENT_MASTER) && c->mstate.cmd_flags & CMD_WRITE)
|
||||
{
|
||||
addReplyError(c,
|
||||
"Transaction contains write commands but instance "
|
||||
"is now a read-only replica. EXEC aborted.");
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
@ -142,7 +159,7 @@ void execCommand(client *c) {
|
||||
orig_argv = c->argv;
|
||||
orig_argc = c->argc;
|
||||
orig_cmd = c->cmd;
|
||||
addReplyMultiBulkLen(c,c->mstate.count);
|
||||
addReplyArrayLen(c,c->mstate.count);
|
||||
for (j = 0; j < c->mstate.count; j++) {
|
||||
c->argc = c->mstate.commands[j].argc;
|
||||
c->argv = c->mstate.commands[j].argv;
|
||||
@ -158,7 +175,7 @@ void execCommand(client *c) {
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
call(c,CMD_CALL_FULL);
|
||||
call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
c->mstate.commands[j].argc = c->argc;
|
||||
|
1368
src/networking.c
1368
src/networking.c
File diff suppressed because it is too large
Load Diff
10
src/notify.c
10
src/notify.c
@ -29,8 +29,8 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* This file implements keyspace events notification via Pub/Sub ad
|
||||
* described at http://redis.io/topics/keyspace-events. */
|
||||
/* This file implements keyspace events notification via Pub/Sub and
|
||||
* described at https://redis.io/topics/notifications. */
|
||||
|
||||
/* Turn a string representing notification classes into an integer
|
||||
* representing notification classes flags xored.
|
||||
@ -55,6 +55,7 @@ int keyspaceEventsStringToFlags(char *classes) {
|
||||
case 'K': flags |= NOTIFY_KEYSPACE; break;
|
||||
case 'E': flags |= NOTIFY_KEYEVENT; break;
|
||||
case 't': flags |= NOTIFY_STREAM; break;
|
||||
case 'm': flags |= NOTIFY_KEY_MISS; break;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
@ -81,6 +82,7 @@ sds keyspaceEventsFlagsToString(int flags) {
|
||||
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
|
||||
if (flags & NOTIFY_KEY_MISS) res = sdscatlen(res,"m",1);
|
||||
}
|
||||
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
@ -100,12 +102,12 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
int len = -1;
|
||||
char buf[24];
|
||||
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
* This bypasses the notifications configuration, but the module engine
|
||||
* will only call event subscribers if the event type matches the types
|
||||
* they are interested in. */
|
||||
moduleNotifyKeyspaceEvent(type, event, key, dbid);
|
||||
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
|
||||
|
139
src/object.c
139
src/object.c
@ -185,7 +185,7 @@ robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
|
||||
/* Duplicate a string object, with the guarantee that the returned object
|
||||
* has the same encoding as the original one.
|
||||
*
|
||||
* This function also guarantees that duplicating a small integere object
|
||||
* This function also guarantees that duplicating a small integer object
|
||||
* (or a string object that contains a representation of a small integer)
|
||||
* will always result in a fresh object that is unshared (refcount == 1).
|
||||
*
|
||||
@ -415,6 +415,18 @@ int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Optimize the SDS string inside the string object to require little space,
|
||||
* in case there is more than 10% of free space at the end of the SDS
|
||||
* string. This happens because SDS strings tend to overallocate to avoid
|
||||
* wasting too much time in allocations when appending to the string. */
|
||||
void trimStringObjectIfNeeded(robj *o) {
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(o->ptr) > sdslen(o->ptr)/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to encode a string object in order to save space */
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
long value;
|
||||
@ -455,10 +467,15 @@ robj *tryObjectEncoding(robj *o) {
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
} else {
|
||||
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
|
||||
o->encoding = OBJ_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
if (o->encoding == OBJ_ENCODING_RAW) {
|
||||
sdsfree(o->ptr);
|
||||
o->encoding = OBJ_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
} else if (o->encoding == OBJ_ENCODING_EMBSTR) {
|
||||
decrRefCount(o);
|
||||
return createStringObjectFromLongLongForValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,11 +501,7 @@ robj *tryObjectEncoding(robj *o) {
|
||||
* We do that only for relatively large strings as this branch
|
||||
* is only entered if the length of the string is greater than
|
||||
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(s) > len/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
trimStringObjectIfNeeded(o);
|
||||
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
@ -826,7 +839,9 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
d = ((zset*)o->ptr)->dict;
|
||||
zskiplist *zsl = ((zset*)o->ptr)->zsl;
|
||||
zskiplistNode *znode = zsl->header->level[0].forward;
|
||||
asize = sizeof(*o)+sizeof(zset)+(sizeof(struct dictEntry*)*dictSlots(d));
|
||||
asize = sizeof(*o)+sizeof(zset)+sizeof(zskiplist)+sizeof(dict)+
|
||||
(sizeof(struct dictEntry*)*dictSlots(d))+
|
||||
zmalloc_size(zsl->header);
|
||||
while(znode != NULL && samples < sample_size) {
|
||||
elesize += sdsAllocSize(znode->ele);
|
||||
elesize += sizeof(struct dictEntry) + zmalloc_size(znode);
|
||||
@ -1011,12 +1026,24 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
|
||||
mem = 0;
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
mem += sdslen(server.aof_buf);
|
||||
mem += sdsalloc(server.aof_buf);
|
||||
mem += aofRewriteBufferSize();
|
||||
}
|
||||
mh->aof_buffer = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = server.lua_scripts_mem;
|
||||
mem += dictSize(server.lua_scripts) * sizeof(dictEntry) +
|
||||
dictSlots(server.lua_scripts) * sizeof(dictEntry*);
|
||||
mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
|
||||
dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
|
||||
if (listLength(server.repl_scriptcache_fifo) > 0) {
|
||||
mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
|
||||
sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo))));
|
||||
}
|
||||
mh->lua_caches = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
long long keyscount = dictSize(db->dict);
|
||||
@ -1074,6 +1101,7 @@ sds getMemoryDoctorReport(void) {
|
||||
int high_alloc_rss = 0; /* High rss overhead. */
|
||||
int big_slave_buf = 0; /* Slave buffers are too big. */
|
||||
int big_client_buf = 0; /* Client buffers are too big. */
|
||||
int many_scripts = 0; /* Script cache has too many scripts. */
|
||||
int num_reports = 0;
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
@ -1124,6 +1152,12 @@ sds getMemoryDoctorReport(void) {
|
||||
big_slave_buf = 1;
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Too many scripts are cached? */
|
||||
if (dictSize(server.lua_scripts) > 1000) {
|
||||
many_scripts = 1;
|
||||
num_reports++;
|
||||
}
|
||||
}
|
||||
|
||||
sds s;
|
||||
@ -1153,14 +1187,17 @@ sds getMemoryDoctorReport(void) {
|
||||
s = sdscatprintf(s," * High allocator RSS overhead: This instance has an RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the allocator is much larger than the sum what the allocator actually holds). This problem is usually due to a large peak memory (check if there is a peak memory entry above in the report), you can try the MEMORY PURGE command to reclaim it.\n\n");
|
||||
}
|
||||
if (high_proc_rss) {
|
||||
s = sdscatprintf(s," * High process RSS overhead: This instance has non-allocator RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the Redis process is much larger than the RSS the allocator holds). This problem may be due to LUA scripts or Modules.\n\n");
|
||||
s = sdscatprintf(s," * High process RSS overhead: This instance has non-allocator RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the Redis process is much larger than the RSS the allocator holds). This problem may be due to Lua scripts or Modules.\n\n");
|
||||
}
|
||||
if (big_slave_buf) {
|
||||
s = sdscat(s," * Big slave buffers: The slave output buffers in this instance are greater than 10MB for each slave (on average). This likely means that there is some slave instance that is struggling receiving data, either because it is too slow or because of networking issues. As a result, data piles on the master output buffers. Please try to identify what slave is not receiving data correctly and why. You can use the INFO output in order to check the slaves delays and the CLIENT LIST command to check the output buffers of each slave.\n\n");
|
||||
s = sdscat(s," * Big replica buffers: The replica output buffers in this instance are greater than 10MB for each replica (on average). This likely means that there is some replica instance that is struggling receiving data, either because it is too slow or because of networking issues. As a result, data piles on the master output buffers. Please try to identify what replica is not receiving data correctly and why. You can use the INFO output in order to check the replicas delays and the CLIENT LIST command to check the output buffers of each replica.\n\n");
|
||||
}
|
||||
if (big_client_buf) {
|
||||
s = sdscat(s," * Big client buffers: The clients output buffers in this instance are greater than 200K per client (on average). This may result from different causes, like Pub/Sub clients subscribed to channels bot not receiving data fast enough, so that data piles on the Redis instance output buffer, or clients sending commands with large replies or very large sequences of commands in the same pipeline. Please use the CLIENT LIST command in order to investigate the issue if it causes problems in your instance, or to understand better why certain clients are using a big amount of memory.\n\n");
|
||||
}
|
||||
if (many_scripts) {
|
||||
s = sdscat(s," * Many scripts: There seem to be many cached scripts in this instance (more than 1000). This may be because scripts are generated and `EVAL`ed, instead of being parameterized (with KEYS and ARGV), `SCRIPT LOAD`ed and `EVALSHA`ed. Unless `SCRIPT FLUSH` is called periodically, the scripts' caches may end up consuming most of your memory.\n\n");
|
||||
}
|
||||
s = sdscat(s,"I'm here to keep you safe, Sam. I want to help you.\n");
|
||||
}
|
||||
freeMemoryOverheadData(mh);
|
||||
@ -1169,7 +1206,7 @@ sds getMemoryDoctorReport(void) {
|
||||
|
||||
/* Set the object LRU/LFU depending on server.maxmemory_policy.
|
||||
* The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* is MAXMEMORY_FLAG_LRU.
|
||||
* Either or both of them may be <0, in that case, nothing is set. */
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
@ -1180,16 +1217,20 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||
}
|
||||
} else if (lru_idle >= 0) {
|
||||
/* Serialized LRU idle time is in seconds. Scale
|
||||
/* Provided LRU idle time is in seconds. Scale
|
||||
* according to the LRU clock resolution this Redis
|
||||
* instance was compiled with (normally 1000 ms, so the
|
||||
* below statement will expand to lru_idle*1000/1000. */
|
||||
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
|
||||
val->lru = lru_clock - lru_idle;
|
||||
/* If the lru field overflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide the maximum
|
||||
* representable idle time. */
|
||||
if (val->lru < 0) val->lru = lru_clock+1;
|
||||
long lru_abs = lru_clock - lru_idle; /* Absolute access time. */
|
||||
/* If the LRU field underflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide a large enough LRU
|
||||
* that is half-way in the circlular LRU clock we use: this way
|
||||
* the computed idle time for this object will stay high for quite
|
||||
* some time. */
|
||||
if (lru_abs < 0)
|
||||
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
||||
val->lru = lru_abs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1226,15 +1267,15 @@ NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->refcount);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
addReplyBulkCString(c,strEncoding(o->encoding));
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
if (server.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.");
|
||||
@ -1242,7 +1283,7 @@ NULL
|
||||
}
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== NULL) return;
|
||||
if (!(server.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.");
|
||||
@ -1254,7 +1295,7 @@ NULL
|
||||
* when the key is read or overwritten. */
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o));
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try OBJECT help", (char *)c->argv[1]->ptr);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1263,9 +1304,18 @@ NULL
|
||||
*
|
||||
* Usage: MEMORY usage <key> */
|
||||
void memoryCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"usage") && c->argc >= 3) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||
const char *help[] = {
|
||||
"DOCTOR - Return memory problems reports.",
|
||||
"MALLOC-STATS -- Return internal statistics report from the memory allocator.",
|
||||
"PURGE -- Attempt to purge dirty pages for reclamation by the allocator.",
|
||||
"STATS -- Return information about the memory usage of the server.",
|
||||
"USAGE <key> [SAMPLES <count>] -- Return memory in bytes used by <key> and its value. Nested values are sampled up to <count> times (default: 5).",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"usage") && c->argc >= 3) {
|
||||
dictEntry *de;
|
||||
long long samples = OBJ_COMPUTE_SIZE_DEF_SAMPLES;
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
if (!strcasecmp(c->argv[j]->ptr,"samples") &&
|
||||
@ -1284,16 +1334,18 @@ void memoryCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
size_t usage = objectComputeSize(o,samples);
|
||||
usage += sdsAllocSize(c->argv[2]->ptr);
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
addReplyNull(c);
|
||||
return;
|
||||
}
|
||||
size_t usage = objectComputeSize(dictGetVal(de),samples);
|
||||
usage += sdsAllocSize(dictGetKey(de));
|
||||
usage += sizeof(dictEntry);
|
||||
addReplyLongLong(c,usage);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
addReplyMultiBulkLen(c,(24+mh->num_dbs)*2);
|
||||
addReplyMapLen(c,25+mh->num_dbs);
|
||||
|
||||
addReplyBulkCString(c,"peak.allocated");
|
||||
addReplyLongLong(c,mh->peak_allocated);
|
||||
@ -1316,11 +1368,14 @@ void memoryCommand(client *c) {
|
||||
addReplyBulkCString(c,"aof.buffer");
|
||||
addReplyLongLong(c,mh->aof_buffer);
|
||||
|
||||
addReplyBulkCString(c,"lua.caches");
|
||||
addReplyLongLong(c,mh->lua_caches);
|
||||
|
||||
for (size_t j = 0; j < mh->num_dbs; j++) {
|
||||
char dbname[32];
|
||||
snprintf(dbname,sizeof(dbname),"db.%zd",mh->db[j].dbid);
|
||||
addReplyBulkCString(c,dbname);
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyMapLen(c,2);
|
||||
|
||||
addReplyBulkCString(c,"overhead.hashtable.main");
|
||||
addReplyLongLong(c,mh->db[j].overhead_ht_main);
|
||||
@ -1409,19 +1464,7 @@ void memoryCommand(client *c) {
|
||||
addReply(c, shared.ok);
|
||||
/* Nothing to do for other allocators. */
|
||||
#endif
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||
addReplyMultiBulkLen(c,5);
|
||||
addReplyBulkCString(c,
|
||||
"MEMORY DOCTOR - Outputs memory problems report");
|
||||
addReplyBulkCString(c,
|
||||
"MEMORY USAGE <key> [SAMPLES <count>] - Estimate memory usage of key");
|
||||
addReplyBulkCString(c,
|
||||
"MEMORY STATS - Show memory usage details");
|
||||
addReplyBulkCString(c,
|
||||
"MEMORY PURGE - Ask the allocator to release memory");
|
||||
addReplyBulkCString(c,
|
||||
"MEMORY MALLOC-STATS - Show allocator internal stats");
|
||||
} else {
|
||||
addReplyError(c,"Syntax error. Try MEMORY HELP");
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
162
src/pubsub.c
162
src/pubsub.c
@ -29,6 +29,93 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
int clientSubscriptionsCount(client *c);
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub client replies API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Send a pubsub message of type "message" to the client. */
|
||||
void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
}
|
||||
|
||||
/* Send a pubsub message of type "pmessage" to the client. The difference
|
||||
* with the "message" type delivered by addReplyPubsubMessage() is that
|
||||
* this message format also includes the pattern that matched the message. */
|
||||
void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[4]);
|
||||
else
|
||||
addReplyPushLen(c,4);
|
||||
addReply(c,shared.pmessagebulk);
|
||||
addReplyBulk(c,pat);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
}
|
||||
|
||||
/* Send the pubsub subscription notification to the client. */
|
||||
void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub unsubscription notification to the client.
|
||||
* Channel can be NULL: this is useful when the client sends a mass
|
||||
* unsubscribe command but there are no channels to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
if (channel)
|
||||
addReplyBulk(c,channel);
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern subscription notification to the client. */
|
||||
void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern unsubscription notification to the client.
|
||||
* Pattern can be NULL: this is useful when the client sends a mass
|
||||
* punsubscribe command but there are no pattern to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
if (pattern)
|
||||
addReplyBulk(c,pattern);
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub low level API
|
||||
*----------------------------------------------------------------------------*/
|
||||
@ -76,10 +163,7 @@ int pubsubSubscribeChannel(client *c, robj *channel) {
|
||||
listAddNodeTail(clients,c);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
addReplyPubsubSubscribed(c,channel);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -111,14 +195,7 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
}
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
|
||||
}
|
||||
if (notify) addReplyPubsubUnsubscribed(c,channel);
|
||||
decrRefCount(channel); /* it is finally safe to release it */
|
||||
return retval;
|
||||
}
|
||||
@ -138,10 +215,7 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
listAddNodeTail(server.pubsub_patterns,pat);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
addReplyPubsubPatSubscribed(c,pattern);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -162,13 +236,7 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
listDelNode(server.pubsub_patterns,ln);
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
|
||||
decrRefCount(pattern);
|
||||
return retval;
|
||||
}
|
||||
@ -186,13 +254,7 @@ int pubsubUnsubscribeAllChannels(client *c, int notify) {
|
||||
count += pubsubUnsubscribeChannel(c,channel,notify);
|
||||
}
|
||||
/* We were subscribed to nothing? Still reply to the client. */
|
||||
if (notify && count == 0) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
if (notify && count == 0) addReplyPubsubUnsubscribed(c,NULL);
|
||||
dictReleaseIterator(di);
|
||||
return count;
|
||||
}
|
||||
@ -210,14 +272,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
|
||||
count += pubsubUnsubscribePattern(c,pattern,notify);
|
||||
}
|
||||
if (notify && count == 0) {
|
||||
/* We were subscribed to nothing? Still reply to the client. */
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
if (notify && count == 0) addReplyPubsubPatUnsubscribed(c,NULL);
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -238,11 +293,7 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
listRewind(list,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = ln->value;
|
||||
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,message);
|
||||
addReplyPubsubMessage(c,channel,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
@ -256,12 +307,10 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
if (stringmatchlen((char*)pat->pattern->ptr,
|
||||
sdslen(pat->pattern->ptr),
|
||||
(char*)channel->ptr,
|
||||
sdslen(channel->ptr),0)) {
|
||||
addReply(pat->client,shared.mbulkhdr[4]);
|
||||
addReply(pat->client,shared.pmessagebulk);
|
||||
addReplyBulk(pat->client,pat->pattern);
|
||||
addReplyBulk(pat->client,channel);
|
||||
addReplyBulk(pat->client,message);
|
||||
sdslen(channel->ptr),0))
|
||||
{
|
||||
addReplyPubsubPatMessage(pat->client,
|
||||
pat->pattern,channel,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
@ -327,9 +376,9 @@ void publishCommand(client *c) {
|
||||
void pubsubCommand(client *c) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"channels [<pattern>] -- Return the currently active channels matching a pattern (default: all).",
|
||||
"numpat -- Return number of subscriptions to patterns.",
|
||||
"numsub [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).",
|
||||
"CHANNELS [<pattern>] -- Return the currently active channels matching a pattern (default: all).",
|
||||
"NUMPAT -- Return number of subscriptions to patterns.",
|
||||
"NUMSUB [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -343,7 +392,7 @@ NULL
|
||||
long mblen = 0;
|
||||
void *replylen;
|
||||
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
replylen = addReplyDeferredLen(c);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *cobj = dictGetKey(de);
|
||||
sds channel = cobj->ptr;
|
||||
@ -356,12 +405,12 @@ NULL
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredMultiBulkLength(c,replylen,mblen);
|
||||
setDeferredArrayLen(c,replylen,mblen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
|
||||
/* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
|
||||
int j;
|
||||
|
||||
addReplyMultiBulkLen(c,(c->argc-2)*2);
|
||||
addReplyArrayLen(c,(c->argc-2)*2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
|
||||
|
||||
@ -372,7 +421,6 @@ NULL
|
||||
/* PUBSUB NUMPAT */
|
||||
addReplyLongLong(c,listLength(server.pubsub_patterns));
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try PUBSUB HELP",
|
||||
(char*)c->argv[1]->ptr);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
@ -1636,7 +1636,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
TEST("add to tail of empty list") {
|
||||
quicklist *ql = quicklistNew(-2, options[_i]);
|
||||
quicklistPushTail(ql, "hello", 6);
|
||||
/* 1 for head and 1 for tail beacuse 1 node = head = tail */
|
||||
/* 1 for head and 1 for tail because 1 node = head = tail */
|
||||
ql_verify(ql, 1, 1, 1, 1);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
@ -1644,7 +1644,7 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
TEST("add to head of empty list") {
|
||||
quicklist *ql = quicklistNew(-2, options[_i]);
|
||||
quicklistPushHead(ql, "hello", 6);
|
||||
/* 1 for head and 1 for tail beacuse 1 node = head = tail */
|
||||
/* 1 for head and 1 for tail because 1 node = head = tail */
|
||||
ql_verify(ql, 1, 1, 1, 1);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@
|
||||
* container: 2 bits, NONE=1, ZIPLIST=2.
|
||||
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
|
||||
* attempted_compress: 1 bit, boolean, used for verifying during testing.
|
||||
* extra: 12 bits, free for future use; pads out the remainder of 32 bits */
|
||||
* extra: 10 bits, free for future use; pads out the remainder of 32 bits */
|
||||
typedef struct quicklistNode {
|
||||
struct quicklistNode *prev;
|
||||
struct quicklistNode *next;
|
||||
|
282
src/rax.c
282
src/rax.c
@ -1,6 +1,6 @@
|
||||
/* Rax -- A radix tree implementation.
|
||||
*
|
||||
* Copyright (c) 2017, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -51,14 +51,18 @@ void *raxNotFound = (void*)"rax-not-found-pointer";
|
||||
|
||||
void raxDebugShowNode(const char *msg, raxNode *n);
|
||||
|
||||
/* Turn debugging messages on/off. */
|
||||
#if 0
|
||||
/* Turn debugging messages on/off by compiling with RAX_DEBUG_MSG macro on.
|
||||
* When RAX_DEBUG_MSG is defined by default Rax operations will emit a lot
|
||||
* of debugging info to the standard output, however you can still turn
|
||||
* debugging on/off in order to enable it only when you suspect there is an
|
||||
* operation causing a bug using the function raxSetDebugMsg(). */
|
||||
#ifdef RAX_DEBUG_MSG
|
||||
#define debugf(...) \
|
||||
do { \
|
||||
if (raxDebugMsg) { \
|
||||
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
|
||||
printf(__VA_ARGS__); \
|
||||
fflush(stdout); \
|
||||
} while (0);
|
||||
}
|
||||
|
||||
#define debugnode(msg,n) raxDebugShowNode(msg,n)
|
||||
#else
|
||||
@ -66,6 +70,16 @@ void raxDebugShowNode(const char *msg, raxNode *n);
|
||||
#define debugnode(msg,n)
|
||||
#endif
|
||||
|
||||
/* By default log debug info if RAX_DEBUG_MSG is defined. */
|
||||
static int raxDebugMsg = 1;
|
||||
|
||||
/* When debug messages are enabled, turn them on/off dynamically. By
|
||||
* default they are enabled. Set the state to 0 to disable, and 1 to
|
||||
* re-enable. */
|
||||
void raxSetDebugMsg(int onoff) {
|
||||
raxDebugMsg = onoff;
|
||||
}
|
||||
|
||||
/* ------------------------- raxStack functions --------------------------
|
||||
* The raxStack is a simple stack of pointers that is capable of switching
|
||||
* from using a stack-allocated array to dynamic heap once a given number of
|
||||
@ -134,12 +148,43 @@ static inline void raxStackFree(raxStack *ts) {
|
||||
* Radix tree implementation
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* Return the padding needed in the characters section of a node having size
|
||||
* 'nodesize'. The padding is needed to store the child pointers to aligned
|
||||
* addresses. Note that we add 4 to the node size because the node has a four
|
||||
* bytes header. */
|
||||
#define raxPadding(nodesize) ((sizeof(void*)-((nodesize+4) % sizeof(void*))) & (sizeof(void*)-1))
|
||||
|
||||
/* Return the pointer to the last child pointer in a node. For the compressed
|
||||
* nodes this is the only child pointer. */
|
||||
#define raxNodeLastChildPtr(n) ((raxNode**) ( \
|
||||
((char*)(n)) + \
|
||||
raxNodeCurrentLength(n) - \
|
||||
sizeof(raxNode*) - \
|
||||
(((n)->iskey && !(n)->isnull) ? sizeof(void*) : 0) \
|
||||
))
|
||||
|
||||
/* Return the pointer to the first child pointer. */
|
||||
#define raxNodeFirstChildPtr(n) ((raxNode**) ( \
|
||||
(n)->data + \
|
||||
(n)->size + \
|
||||
raxPadding((n)->size)))
|
||||
|
||||
/* Return the current total size of the node. Note that the second line
|
||||
* computes the padding after the string of characters, needed in order to
|
||||
* save pointers to aligned addresses. */
|
||||
#define raxNodeCurrentLength(n) ( \
|
||||
sizeof(raxNode)+(n)->size+ \
|
||||
raxPadding((n)->size)+ \
|
||||
((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \
|
||||
(((n)->iskey && !(n)->isnull)*sizeof(void*)) \
|
||||
)
|
||||
|
||||
/* Allocate a new non compressed node with the specified number of children.
|
||||
* If datafiled is true, the allocation is made large enough to hold the
|
||||
* associated data pointer.
|
||||
* Returns the new node pointer. On out of memory NULL is returned. */
|
||||
raxNode *raxNewNode(size_t children, int datafield) {
|
||||
size_t nodesize = sizeof(raxNode)+children+
|
||||
size_t nodesize = sizeof(raxNode)+children+raxPadding(children)+
|
||||
sizeof(raxNode*)*children;
|
||||
if (datafield) nodesize += sizeof(void*);
|
||||
raxNode *node = rax_malloc(nodesize);
|
||||
@ -167,13 +212,6 @@ rax *raxNew(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the current total size of the node. */
|
||||
#define raxNodeCurrentLength(n) ( \
|
||||
sizeof(raxNode)+(n)->size+ \
|
||||
((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \
|
||||
(((n)->iskey && !(n)->isnull)*sizeof(void*)) \
|
||||
)
|
||||
|
||||
/* realloc the node to make room for auxiliary data in order
|
||||
* to store an item in that node. On out of memory NULL is returned. */
|
||||
raxNode *raxReallocForData(raxNode *n, void *data) {
|
||||
@ -216,18 +254,17 @@ void *raxGetData(raxNode *n) {
|
||||
raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode ***parentlink) {
|
||||
assert(n->iscompr == 0);
|
||||
|
||||
size_t curlen = sizeof(raxNode)+
|
||||
n->size+
|
||||
sizeof(raxNode*)*n->size;
|
||||
size_t newlen;
|
||||
size_t curlen = raxNodeCurrentLength(n);
|
||||
n->size++;
|
||||
size_t newlen = raxNodeCurrentLength(n);
|
||||
n->size--; /* For now restore the orignal size. We'll update it only on
|
||||
success at the end. */
|
||||
|
||||
/* Alloc the new child we will link to 'n'. */
|
||||
raxNode *child = raxNewNode(0,0);
|
||||
if (child == NULL) return NULL;
|
||||
|
||||
/* Make space in the original node. */
|
||||
if (n->iskey) curlen += sizeof(void*);
|
||||
newlen = curlen+sizeof(raxNode*)+1; /* Add 1 char and 1 pointer. */
|
||||
raxNode *newn = rax_realloc(n,newlen);
|
||||
if (newn == NULL) {
|
||||
rax_free(child);
|
||||
@ -235,14 +272,34 @@ raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode **
|
||||
}
|
||||
n = newn;
|
||||
|
||||
/* After the reallocation, we have 5/9 (depending on the system
|
||||
* pointer size) bytes at the end, that is, the additional char
|
||||
* in the 'data' section, plus one pointer to the new child:
|
||||
/* After the reallocation, we have up to 8/16 (depending on the system
|
||||
* pointer size, and the required node padding) bytes at the end, that is,
|
||||
* the additional char in the 'data' section, plus one pointer to the new
|
||||
* child, plus the padding needed in order to store addresses into aligned
|
||||
* locations.
|
||||
*
|
||||
* [numc][abx][ap][bp][xp]|auxp|.....
|
||||
* So if we start with the following node, having "abde" edges.
|
||||
*
|
||||
* Note:
|
||||
* - We assume 4 bytes pointer for simplicity.
|
||||
* - Each space below corresponds to one byte
|
||||
*
|
||||
* [HDR*][abde][Aptr][Bptr][Dptr][Eptr]|AUXP|
|
||||
*
|
||||
* After the reallocation we need: 1 byte for the new edge character
|
||||
* plus 4 bytes for a new child pointer (assuming 32 bit machine).
|
||||
* However after adding 1 byte to the edge char, the header + the edge
|
||||
* characters are no longer aligned, so we also need 3 bytes of padding.
|
||||
* In total the reallocation will add 1+4+3 bytes = 8 bytes:
|
||||
*
|
||||
* (Blank bytes are represented by ".")
|
||||
*
|
||||
* [HDR*][abde][Aptr][Bptr][Dptr][Eptr]|AUXP|[....][....]
|
||||
*
|
||||
* Let's find where to insert the new child in order to make sure
|
||||
* it is inserted in-place lexicographically. */
|
||||
* it is inserted in-place lexicographically. Assuming we are adding
|
||||
* a child "c" in our case pos will be = 2 after the end of the following
|
||||
* loop. */
|
||||
int pos;
|
||||
for (pos = 0; pos < n->size; pos++) {
|
||||
if (n->data[pos] > c) break;
|
||||
@ -252,55 +309,81 @@ raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode **
|
||||
* so that we can mess with the other data without overwriting it.
|
||||
* We will obtain something like that:
|
||||
*
|
||||
* [numc][abx][ap][bp][xp].....|auxp| */
|
||||
unsigned char *src;
|
||||
* [HDR*][abde][Aptr][Bptr][Dptr][Eptr][....][....]|AUXP|
|
||||
*/
|
||||
unsigned char *src, *dst;
|
||||
if (n->iskey && !n->isnull) {
|
||||
src = n->data+n->size+sizeof(raxNode*)*n->size;
|
||||
memmove(src+1+sizeof(raxNode*),src,sizeof(void*));
|
||||
src = ((unsigned char*)n+curlen-sizeof(void*));
|
||||
dst = ((unsigned char*)n+newlen-sizeof(void*));
|
||||
memmove(dst,src,sizeof(void*));
|
||||
}
|
||||
|
||||
/* Now imagine we are adding a node with edge 'c'. The insertion
|
||||
* point is between 'b' and 'x', so the 'pos' variable value is
|
||||
* To start, move all the child pointers after the insertion point
|
||||
* of 1+sizeof(pointer) bytes on the right, to obtain:
|
||||
/* Compute the "shift", that is, how many bytes we need to move the
|
||||
* pointers section forward because of the addition of the new child
|
||||
* byte in the string section. Note that if we had no padding, that
|
||||
* would be always "1", since we are adding a single byte in the string
|
||||
* section of the node (where now there is "abde" basically).
|
||||
*
|
||||
* [numc][abx][ap][bp].....[xp]|auxp| */
|
||||
src = n->data+n->size+sizeof(raxNode*)*pos;
|
||||
memmove(src+1+sizeof(raxNode*),src,sizeof(raxNode*)*(n->size-pos));
|
||||
* However we have padding, so it could be zero, or up to 8.
|
||||
*
|
||||
* Another way to think at the shift is, how many bytes we need to
|
||||
* move child pointers forward *other than* the obvious sizeof(void*)
|
||||
* needed for the additional pointer itself. */
|
||||
size_t shift = newlen - curlen - sizeof(void*);
|
||||
|
||||
/* We said we are adding a node with edge 'c'. The insertion
|
||||
* point is between 'b' and 'd', so the 'pos' variable value is
|
||||
* the index of the first child pointer that we need to move forward
|
||||
* to make space for our new pointer.
|
||||
*
|
||||
* To start, move all the child pointers after the insertion point
|
||||
* of shift+sizeof(pointer) bytes on the right, to obtain:
|
||||
*
|
||||
* [HDR*][abde][Aptr][Bptr][....][....][Dptr][Eptr]|AUXP|
|
||||
*/
|
||||
src = n->data+n->size+
|
||||
raxPadding(n->size)+
|
||||
sizeof(raxNode*)*pos;
|
||||
memmove(src+shift+sizeof(raxNode*),src,sizeof(raxNode*)*(n->size-pos));
|
||||
|
||||
/* Move the pointers to the left of the insertion position as well. Often
|
||||
* we don't need to do anything if there was already some padding to use. In
|
||||
* that case the final destination of the pointers will be the same, however
|
||||
* in our example there was no pre-existing padding, so we added one byte
|
||||
* plus thre bytes of padding. After the next memmove() things will look
|
||||
* like thata:
|
||||
*
|
||||
* [HDR*][abde][....][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
|
||||
*/
|
||||
if (shift) {
|
||||
src = (unsigned char*) raxNodeFirstChildPtr(n);
|
||||
memmove(src+shift,src,sizeof(raxNode*)*pos);
|
||||
}
|
||||
|
||||
/* Now make the space for the additional char in the data section,
|
||||
* but also move the pointers before the insertion point in the right
|
||||
* by 1 byte, in order to obtain the following:
|
||||
* but also move the pointers before the insertion point to the right
|
||||
* by shift bytes, in order to obtain the following:
|
||||
*
|
||||
* [numc][ab.x][ap][bp]....[xp]|auxp| */
|
||||
* [HDR*][ab.d][e...][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
|
||||
*/
|
||||
src = n->data+pos;
|
||||
memmove(src+1,src,n->size-pos+sizeof(raxNode*)*pos);
|
||||
memmove(src+1,src,n->size-pos);
|
||||
|
||||
/* We can now set the character and its child node pointer to get:
|
||||
*
|
||||
* [numc][abcx][ap][bp][cp]....|auxp|
|
||||
* [numc][abcx][ap][bp][cp][xp]|auxp| */
|
||||
* [HDR*][abcd][e...][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
|
||||
* [HDR*][abcd][e...][Aptr][Bptr][Cptr][Dptr][Eptr]|AUXP|
|
||||
*/
|
||||
n->data[pos] = c;
|
||||
n->size++;
|
||||
raxNode **childfield = (raxNode**)(n->data+n->size+sizeof(raxNode*)*pos);
|
||||
src = (unsigned char*) raxNodeFirstChildPtr(n);
|
||||
raxNode **childfield = (raxNode**)(src+sizeof(raxNode*)*pos);
|
||||
memcpy(childfield,&child,sizeof(child));
|
||||
*childptr = child;
|
||||
*parentlink = childfield;
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Return the pointer to the last child pointer in a node. For the compressed
|
||||
* nodes this is the only child pointer. */
|
||||
#define raxNodeLastChildPtr(n) ((raxNode**) ( \
|
||||
((char*)(n)) + \
|
||||
raxNodeCurrentLength(n) - \
|
||||
sizeof(raxNode*) - \
|
||||
(((n)->iskey && !(n)->isnull) ? sizeof(void*) : 0) \
|
||||
))
|
||||
|
||||
/* Return the pointer to the first child pointer. */
|
||||
#define raxNodeFirstChildPtr(n) ((raxNode**)((n)->data+(n)->size))
|
||||
|
||||
/* Turn the node 'n', that must be a node without any children, into a
|
||||
* compressed node representing a set of nodes linked one after the other
|
||||
* and having exactly one child each. The node can be a key or not: this
|
||||
@ -321,7 +404,7 @@ raxNode *raxCompressNode(raxNode *n, unsigned char *s, size_t len, raxNode **chi
|
||||
if (*child == NULL) return NULL;
|
||||
|
||||
/* Make space in the parent node. */
|
||||
newsize = sizeof(raxNode)+len+sizeof(raxNode*);
|
||||
newsize = sizeof(raxNode)+len+raxPadding(len)+sizeof(raxNode*);
|
||||
if (n->iskey) {
|
||||
data = raxGetData(n); /* To restore it later. */
|
||||
if (!n->isnull) newsize += sizeof(void*);
|
||||
@ -467,7 +550,7 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
/* If the node we stopped at is a compressed node, we need to
|
||||
* split it before to continue.
|
||||
*
|
||||
* Splitting a compressed node have a few possibile cases.
|
||||
* Splitting a compressed node have a few possible cases.
|
||||
* Imagine that the node 'h' we are currently at is a compressed
|
||||
* node contaning the string "ANNIBALE" (it means that it represents
|
||||
* nodes A -> N -> N -> I -> B -> A -> L -> E with the only child
|
||||
@ -619,13 +702,14 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
raxNode *postfix = NULL;
|
||||
|
||||
if (trimmedlen) {
|
||||
nodesize = sizeof(raxNode)+trimmedlen+sizeof(raxNode*);
|
||||
nodesize = sizeof(raxNode)+trimmedlen+raxPadding(trimmedlen)+
|
||||
sizeof(raxNode*);
|
||||
if (h->iskey && !h->isnull) nodesize += sizeof(void*);
|
||||
trimmed = rax_malloc(nodesize);
|
||||
}
|
||||
|
||||
if (postfixlen) {
|
||||
nodesize = sizeof(raxNode)+postfixlen+
|
||||
nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+
|
||||
sizeof(raxNode*);
|
||||
postfix = rax_malloc(nodesize);
|
||||
}
|
||||
@ -701,11 +785,12 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
|
||||
/* Allocate postfix & trimmed nodes ASAP to fail for OOM gracefully. */
|
||||
size_t postfixlen = h->size - j;
|
||||
size_t nodesize = sizeof(raxNode)+postfixlen+sizeof(raxNode*);
|
||||
size_t nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+
|
||||
sizeof(raxNode*);
|
||||
if (data != NULL) nodesize += sizeof(void*);
|
||||
raxNode *postfix = rax_malloc(nodesize);
|
||||
|
||||
nodesize = sizeof(raxNode)+j+sizeof(raxNode*);
|
||||
nodesize = sizeof(raxNode)+j+raxPadding(j)+sizeof(raxNode*);
|
||||
if (h->iskey && !h->isnull) nodesize += sizeof(void*);
|
||||
raxNode *trimmed = rax_malloc(nodesize);
|
||||
|
||||
@ -749,7 +834,7 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
cp = raxNodeLastChildPtr(trimmed);
|
||||
memcpy(cp,&postfix,sizeof(postfix));
|
||||
|
||||
/* Finish! We don't need to contine with the insertion
|
||||
/* Finish! We don't need to continue with the insertion
|
||||
* algorithm for ALGO 2. The key is already inserted. */
|
||||
rax->numele++;
|
||||
rax_free(h);
|
||||
@ -875,7 +960,7 @@ raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/* Otherwise we need to scan for the children pointer and memmove()
|
||||
/* Otherwise we need to scan for the child pointer and memmove()
|
||||
* accordingly.
|
||||
*
|
||||
* 1. To start we seek the first element in both the children
|
||||
@ -900,13 +985,21 @@ raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
|
||||
debugf("raxRemoveChild tail len: %d\n", taillen);
|
||||
memmove(e,e+1,taillen);
|
||||
|
||||
/* Since we have one data byte less, also child pointers start one byte
|
||||
* before now. */
|
||||
memmove(((char*)cp)-1,cp,(parent->size-taillen-1)*sizeof(raxNode**));
|
||||
/* Compute the shift, that is the amount of bytes we should move our
|
||||
* child pointers to the left, since the removal of one edge character
|
||||
* and the corresponding padding change, may change the layout.
|
||||
* We just check if in the old version of the node there was at the
|
||||
* end just a single byte and all padding: in that case removing one char
|
||||
* will remove a whole sizeof(void*) word. */
|
||||
size_t shift = ((parent->size+4) % sizeof(void*)) == 1 ? sizeof(void*) : 0;
|
||||
|
||||
/* Move the remaining "tail" pointer at the right position as well. */
|
||||
/* Move the children pointers before the deletion point. */
|
||||
if (shift)
|
||||
memmove(((char*)cp)-shift,cp,(parent->size-taillen-1)*sizeof(raxNode**));
|
||||
|
||||
/* Move the remaining "tail" pointers at the right position as well. */
|
||||
size_t valuelen = (parent->iskey && !parent->isnull) ? sizeof(void*) : 0;
|
||||
memmove(((char*)c)-1,c+1,taillen*sizeof(raxNode**)+valuelen);
|
||||
memmove(((char*)c)-shift,c+1,taillen*sizeof(raxNode**)+valuelen);
|
||||
|
||||
/* 4. Update size. */
|
||||
parent->size--;
|
||||
@ -1072,7 +1165,7 @@ int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
|
||||
if (nodes > 1) {
|
||||
/* If we can compress, create the new node and populate it. */
|
||||
size_t nodesize =
|
||||
sizeof(raxNode)+comprsize+sizeof(raxNode*);
|
||||
sizeof(raxNode)+comprsize+raxPadding(comprsize)+sizeof(raxNode*);
|
||||
raxNode *new = rax_malloc(nodesize);
|
||||
/* An out of memory here just means we cannot optimize this
|
||||
* node, but the tree is left in a consistent state. */
|
||||
@ -1167,6 +1260,7 @@ void raxStart(raxIterator *it, rax *rt) {
|
||||
it->key = it->key_static_string;
|
||||
it->key_max = RAX_ITER_STATIC_LEN;
|
||||
it->data = NULL;
|
||||
it->node_cb = NULL;
|
||||
raxStackInit(&it->stack);
|
||||
}
|
||||
|
||||
@ -1240,6 +1334,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
if (!raxIteratorAddChars(it,it->node->data,
|
||||
it->node->iscompr ? it->node->size : 1)) return 0;
|
||||
memcpy(&it->node,cp,sizeof(it->node));
|
||||
/* Call the node callback if any, and replace the node pointer
|
||||
* if the callback returns true. */
|
||||
if (it->node_cb && it->node_cb(&it->node))
|
||||
memcpy(cp,&it->node,sizeof(it->node));
|
||||
/* For "next" step, stop every time we find a key along the
|
||||
* way, since the key is lexicograhically smaller compared to
|
||||
* what follows in the sub-children. */
|
||||
@ -1292,6 +1390,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
raxIteratorAddChars(it,it->node->data+i,1);
|
||||
if (!raxStackPush(&it->stack,it->node)) return 0;
|
||||
memcpy(&it->node,cp,sizeof(it->node));
|
||||
/* Call the node callback if any, and replace the node
|
||||
* pointer if the callback returns true. */
|
||||
if (it->node_cb && it->node_cb(&it->node))
|
||||
memcpy(cp,&it->node,sizeof(it->node));
|
||||
if (it->node->iskey) {
|
||||
it->data = raxGetData(it->node);
|
||||
return 1;
|
||||
@ -1304,7 +1406,7 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Seek the grestest key in the subtree at the current node. Return 0 on
|
||||
/* Seek the greatest key in the subtree at the current node. Return 0 on
|
||||
* out of memory, otherwise 1. This is an helper function for different
|
||||
* iteration functions below. */
|
||||
int raxSeekGreatest(raxIterator *it) {
|
||||
@ -1325,7 +1427,7 @@ int raxSeekGreatest(raxIterator *it) {
|
||||
|
||||
/* Like raxIteratorNextStep() but implements an iteration step moving
|
||||
* to the lexicographically previous element. The 'noup' option has a similar
|
||||
* effect to the one of raxIteratorPrevSte(). */
|
||||
* effect to the one of raxIteratorNextStep(). */
|
||||
int raxIteratorPrevStep(raxIterator *it, int noup) {
|
||||
if (it->flags & RAX_ITER_EOF) {
|
||||
return 1;
|
||||
@ -1784,6 +1886,7 @@ void raxShow(rax *rax) {
|
||||
|
||||
/* Used by debugnode() macro to show info about a given node. */
|
||||
void raxDebugShowNode(const char *msg, raxNode *n) {
|
||||
if (raxDebugMsg == 0) return;
|
||||
printf("%s: %p [%.*s] key:%d size:%d children:",
|
||||
msg, (void*)n, (int)n->size, (char*)n->data, n->iskey, n->size);
|
||||
int numcld = n->iscompr ? 1 : n->size;
|
||||
@ -1798,4 +1901,43 @@ void raxDebugShowNode(const char *msg, raxNode *n) {
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Touch all the nodes of a tree returning a check sum. This is useful
|
||||
* in order to make Valgrind detect if there is something wrong while
|
||||
* reading the data structure.
|
||||
*
|
||||
* This function was used in order to identify Rax bugs after a big refactoring
|
||||
* using this technique:
|
||||
*
|
||||
* 1. The rax-test is executed using Valgrind, adding a printf() so that for
|
||||
* the fuzz tester we see what iteration in the loop we are in.
|
||||
* 2. After every modification of the radix tree made by the fuzz tester
|
||||
* in rax-test.c, we add a call to raxTouch().
|
||||
* 3. Now as soon as an operation will corrupt the tree, raxTouch() will
|
||||
* detect it (via Valgrind) immediately. We can add more calls to narrow
|
||||
* the state.
|
||||
* 4. At this point a good idea is to enable Rax debugging messages immediately
|
||||
* before the moment the tree is corrupted, to see what happens.
|
||||
*/
|
||||
unsigned long raxTouch(raxNode *n) {
|
||||
debugf("Touching %p\n", (void*)n);
|
||||
unsigned long sum = 0;
|
||||
if (n->iskey) {
|
||||
sum += (unsigned long)raxGetData(n);
|
||||
}
|
||||
|
||||
int numchildren = n->iscompr ? 1 : n->size;
|
||||
raxNode **cp = raxNodeFirstChildPtr(n);
|
||||
int count = 0;
|
||||
for (int i = 0; i < numchildren; i++) {
|
||||
if (numchildren > 1) {
|
||||
sum += (long)n->data[i];
|
||||
}
|
||||
raxNode *child;
|
||||
memcpy(&child,cp,sizeof(child));
|
||||
if (child == (void*)0x65d1760) count++;
|
||||
if (count > 1) exit(1);
|
||||
sum += raxTouch(child);
|
||||
cp++;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
60
src/rax.h
60
src/rax.h
@ -1,3 +1,33 @@
|
||||
/* Rax -- A radix tree implementation.
|
||||
*
|
||||
* Copyright (c) 2017-2018, 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.
|
||||
*/
|
||||
|
||||
#ifndef RAX_H
|
||||
#define RAX_H
|
||||
|
||||
@ -77,16 +107,16 @@ typedef struct raxNode {
|
||||
* Note how the character is not stored in the children but in the
|
||||
* edge of the parents:
|
||||
*
|
||||
* [header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
|
||||
* [header iscompr=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
|
||||
*
|
||||
* if node is compressed (strlen != 0) the node has 1 children.
|
||||
* if node is compressed (iscompr bit is 1) the node has 1 children.
|
||||
* In that case the 'size' bytes of the string stored immediately at
|
||||
* the start of the data section, represent a sequence of successive
|
||||
* nodes linked one after the other, for which only the last one in
|
||||
* the sequence is actually represented as a node, and pointed to by
|
||||
* the current compressed node.
|
||||
*
|
||||
* [header strlen=3][xyz][z-ptr](value-ptr?)
|
||||
* [header iscompr=1][xyz][z-ptr](value-ptr?)
|
||||
*
|
||||
* Both compressed and not compressed nodes can represent a key
|
||||
* with associated data in the radix tree at any level (not just terminal
|
||||
@ -94,7 +124,7 @@ typedef struct raxNode {
|
||||
*
|
||||
* If the node has an associated key (iskey=1) and is not NULL
|
||||
* (isnull=0), then after the raxNode pointers poiting to the
|
||||
* childen, an additional value pointer is present (as you can see
|
||||
* children, an additional value pointer is present (as you can see
|
||||
* in the representation above as "value-ptr" field).
|
||||
*/
|
||||
unsigned char data[];
|
||||
@ -119,6 +149,21 @@ typedef struct raxStack {
|
||||
int oom; /* True if pushing into this stack failed for OOM at some point. */
|
||||
} raxStack;
|
||||
|
||||
/* Optional callback used for iterators and be notified on each rax node,
|
||||
* including nodes not representing keys. If the callback returns true
|
||||
* the callback changed the node pointer in the iterator structure, and the
|
||||
* iterator implementation will have to replace the pointer in the radix tree
|
||||
* internals. This allows the callback to reallocate the node to perform
|
||||
* very special operations, normally not needed by normal applications.
|
||||
*
|
||||
* This callback is used to perform very low level analysis of the radix tree
|
||||
* structure, scanning each possible node (but the root node), or in order to
|
||||
* reallocate the nodes to reduce the allocation fragmentation (this is the
|
||||
* Redis application for this callback).
|
||||
*
|
||||
* This is currently only supported in forward iterations (raxNext) */
|
||||
typedef int (*raxNodeCallback)(raxNode **noderef);
|
||||
|
||||
/* Radix tree iterator state is encapsulated into this data structure. */
|
||||
#define RAX_ITER_STATIC_LEN 128
|
||||
#define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current
|
||||
@ -137,6 +182,7 @@ typedef struct raxIterator {
|
||||
unsigned char key_static_string[RAX_ITER_STATIC_LEN];
|
||||
raxNode *node; /* Current node. Only for unsafe iteration. */
|
||||
raxStack stack; /* Stack used for unsafe iteration. */
|
||||
raxNodeCallback node_cb; /* Optional node callback. Normally set to NULL. */
|
||||
} raxIterator;
|
||||
|
||||
/* A special pointer returned for not found items. */
|
||||
@ -160,5 +206,11 @@ void raxStop(raxIterator *it);
|
||||
int raxEOF(raxIterator *it);
|
||||
void raxShow(rax *rax);
|
||||
uint64_t raxSize(rax *rax);
|
||||
unsigned long raxTouch(raxNode *n);
|
||||
void raxSetDebugMsg(int onoff);
|
||||
|
||||
/* Internal API. May be used by the node callback in order to access rax nodes
|
||||
* in a low level way, so this function is exported as well. */
|
||||
void raxSetData(raxNode *n, void *data);
|
||||
|
||||
#endif
|
||||
|
342
src/rdb.c
342
src/rdb.c
@ -42,30 +42,41 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#define rdbExitReportCorruptRDB(...) rdbCheckThenExit(__LINE__,__VA_ARGS__)
|
||||
/* This macro is called when the internal RDB stracture is corrupt */
|
||||
#define rdbExitReportCorruptRDB(...) rdbReportError(1, __LINE__,__VA_ARGS__)
|
||||
/* This macro is called when RDB read failed (possibly a short read) */
|
||||
#define rdbReportReadError(...) rdbReportError(0, __LINE__,__VA_ARGS__)
|
||||
|
||||
char* rdbFileBeingLoaded = NULL; /* used for rdb checking on read error */
|
||||
extern int rdbCheckMode;
|
||||
void rdbCheckError(const char *fmt, ...);
|
||||
void rdbCheckSetError(const char *fmt, ...);
|
||||
|
||||
void rdbCheckThenExit(int linenum, char *reason, ...) {
|
||||
void rdbReportError(int corruption_error, int linenum, char *reason, ...) {
|
||||
va_list ap;
|
||||
char msg[1024];
|
||||
int len;
|
||||
|
||||
len = snprintf(msg,sizeof(msg),
|
||||
"Internal error in RDB reading function at rdb.c:%d -> ", linenum);
|
||||
"Internal error in RDB reading offset %llu, function at rdb.c:%d -> ",
|
||||
(unsigned long long)server.loading_loaded_bytes, linenum);
|
||||
va_start(ap,reason);
|
||||
vsnprintf(msg+len,sizeof(msg)-len,reason,ap);
|
||||
va_end(ap);
|
||||
|
||||
if (!rdbCheckMode) {
|
||||
serverLog(LL_WARNING, "%s", msg);
|
||||
char *argv[2] = {"",server.rdb_filename};
|
||||
redis_check_rdb_main(2,argv,NULL);
|
||||
if (rdbFileBeingLoaded || corruption_error) {
|
||||
serverLog(LL_WARNING, "%s", msg);
|
||||
char *argv[2] = {"",rdbFileBeingLoaded};
|
||||
redis_check_rdb_main(2,argv,NULL);
|
||||
} else {
|
||||
serverLog(LL_WARNING, "%s. Failure loading rdb format from socket, assuming connection error, resuming operation.", msg);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
rdbCheckError("%s",msg);
|
||||
}
|
||||
serverLog(LL_WARNING, "Terminating server after rdb file reading failure.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -75,18 +86,6 @@ static int rdbWriteRaw(rio *rdb, void *p, size_t len) {
|
||||
return len;
|
||||
}
|
||||
|
||||
/* This is just a wrapper for the low level function rioRead() that will
|
||||
* automatically abort if it is not possible to read the specified amount
|
||||
* of bytes. */
|
||||
void rdbLoadRaw(rio *rdb, void *buf, uint64_t len) {
|
||||
if (rioRead(rdb,buf,len) == 0) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Impossible to read %llu bytes in rdbLoadRaw()",
|
||||
(unsigned long long) len);
|
||||
return; /* Not reached. */
|
||||
}
|
||||
}
|
||||
|
||||
int rdbSaveType(rio *rdb, unsigned char type) {
|
||||
return rdbWriteRaw(rdb,&type,1);
|
||||
}
|
||||
@ -102,10 +101,12 @@ int rdbLoadType(rio *rdb) {
|
||||
|
||||
/* This is only used to load old databases stored with the RDB_OPCODE_EXPIRETIME
|
||||
* opcode. New versions of Redis store using the RDB_OPCODE_EXPIRETIME_MS
|
||||
* opcode. */
|
||||
* opcode. On error -1 is returned, however this could be a valid time, so
|
||||
* to check for loading errors the caller should call rioGetReadError() after
|
||||
* calling this function. */
|
||||
time_t rdbLoadTime(rio *rdb) {
|
||||
int32_t t32;
|
||||
rdbLoadRaw(rdb,&t32,4);
|
||||
if (rioRead(rdb,&t32,4) == 0) return -1;
|
||||
return (time_t)t32;
|
||||
}
|
||||
|
||||
@ -125,10 +126,14 @@ int rdbSaveMillisecondTime(rio *rdb, long long t) {
|
||||
* after upgrading to Redis version 5 they will no longer be able to load their
|
||||
* own old RDB files. Because of that, we instead fix the function only for new
|
||||
* RDB versions, and load older RDB versions as we used to do in the past,
|
||||
* allowing big endian systems to load their own old RDB files. */
|
||||
* allowing big endian systems to load their own old RDB files.
|
||||
*
|
||||
* On I/O error the function returns LLONG_MAX, however if this is also a
|
||||
* valid stored value, the caller should use rioGetReadError() to check for
|
||||
* errors after calling this function. */
|
||||
long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
|
||||
int64_t t64;
|
||||
rdbLoadRaw(rdb,&t64,8);
|
||||
if (rioRead(rdb,&t64,8) == 0) return LLONG_MAX;
|
||||
if (rdbver >= 9) /* Check the top comment of this function. */
|
||||
memrev64ifbe(&t64); /* Convert in big endian if the system is BE. */
|
||||
return (long long)t64;
|
||||
@ -277,8 +282,8 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||
v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24);
|
||||
val = (int32_t)v;
|
||||
} else {
|
||||
val = 0; /* anti-warning */
|
||||
rdbExitReportCorruptRDB("Unknown RDB integer encoding type %d",enctype);
|
||||
return NULL; /* Never reached. */
|
||||
}
|
||||
if (plain || sds) {
|
||||
char buf[LONG_STR_SIZE], *p;
|
||||
@ -288,7 +293,7 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||
memcpy(p,buf,len);
|
||||
return p;
|
||||
} else if (encode) {
|
||||
return createStringObjectFromLongLong(val);
|
||||
return createStringObjectFromLongLongForValue(val);
|
||||
} else {
|
||||
return createObject(OBJ_STRING,sdsfromlonglong(val));
|
||||
}
|
||||
@ -381,8 +386,7 @@ void *rdbLoadLzfStringObject(rio *rdb, int flags, size_t *lenptr) {
|
||||
/* Load the compressed representation and uncompress it to target. */
|
||||
if (rioRead(rdb,c,clen) == 0) goto err;
|
||||
if (lzf_decompress(c,clen,val,len) == 0) {
|
||||
if (rdbCheckMode) rdbCheckSetError("Invalid LZF compressed string");
|
||||
goto err;
|
||||
rdbExitReportCorruptRDB("Invalid LZF compressed string");
|
||||
}
|
||||
zfree(c);
|
||||
|
||||
@ -496,6 +500,7 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
|
||||
return rdbLoadLzfStringObject(rdb,flags,lenptr);
|
||||
default:
|
||||
rdbExitReportCorruptRDB("Unknown RDB string encoding type %d",len);
|
||||
return NULL; /* Never reached. */
|
||||
}
|
||||
}
|
||||
|
||||
@ -751,7 +756,7 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
|
||||
|
||||
/* Save a Redis object.
|
||||
* Returns -1 on error, number of bytes written on success. */
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
ssize_t n = 0, nwritten = 0;
|
||||
|
||||
if (o->type == OBJ_STRING) {
|
||||
@ -966,7 +971,6 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
|
||||
/* Write the "module" identifier as prefix, so that we'll be able
|
||||
* to call the right module during loading. */
|
||||
@ -975,10 +979,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
io.bytes += retval;
|
||||
|
||||
/* Then write the module-specific representation + EOF marker. */
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
mt->rdb_save(&io,mv->value);
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
if (retval == -1)
|
||||
io.error = 1;
|
||||
else
|
||||
io.bytes += retval;
|
||||
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
@ -996,7 +1003,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
size_t rdbSavedObjectLen(robj *o) {
|
||||
ssize_t len = rdbSaveObject(NULL,o);
|
||||
ssize_t len = rdbSaveObject(NULL,o,NULL);
|
||||
serverAssertWithInfo(NULL,o,len != -1);
|
||||
return len;
|
||||
}
|
||||
@ -1038,7 +1045,12 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveObjectType(rdb,val) == -1) return -1;
|
||||
if (rdbSaveStringObject(rdb,key) == -1) return -1;
|
||||
if (rdbSaveObject(rdb,val) == -1) return -1;
|
||||
if (rdbSaveObject(rdb,val,key) == -1) return -1;
|
||||
|
||||
/* Delay return if required (for testing) */
|
||||
if (server.rdb_key_save_delay)
|
||||
usleep(server.rdb_key_save_delay);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1091,6 +1103,40 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
|
||||
/* Save a module-specific aux value. */
|
||||
RedisModuleIO io;
|
||||
int retval = rdbSaveType(rdb, RDB_OPCODE_MODULE_AUX);
|
||||
|
||||
/* Write the "module" identifier as prefix, so that we'll be able
|
||||
* to call the right module during loading. */
|
||||
retval = rdbSaveLen(rdb,mt->id);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* write the 'when' so that we can provide it on loading */
|
||||
retval = rdbSaveLen(rdb,when);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* Then write the module-specific representation + EOF marker. */
|
||||
moduleInitIOContext(io,mt,rdb,NULL);
|
||||
mt->aux_save(&io,when);
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||
if (retval == -1)
|
||||
io.error = 1;
|
||||
else
|
||||
io.bytes += retval;
|
||||
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
if (io.error)
|
||||
return -1;
|
||||
return io.bytes;
|
||||
}
|
||||
|
||||
/* Produces a dump of the database in RDB format sending it to the specified
|
||||
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR
|
||||
* is returned and part of the output, or all the output, can be
|
||||
@ -1112,6 +1158,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
||||
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
||||
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
|
||||
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
redisDb *db = server.db+j;
|
||||
@ -1173,6 +1220,8 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
di = NULL; /* So that we don't release it again on error. */
|
||||
}
|
||||
|
||||
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
|
||||
|
||||
/* EOF opcode */
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
||||
|
||||
@ -1380,7 +1429,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@ -1632,6 +1681,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
default:
|
||||
/* totally unreachable */
|
||||
rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
|
||||
break;
|
||||
}
|
||||
@ -1639,12 +1689,22 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
o = createStreamObject();
|
||||
stream *s = o->ptr;
|
||||
uint64_t listpacks = rdbLoadLen(rdb,NULL);
|
||||
if (listpacks == RDB_LENERR) {
|
||||
rdbReportReadError("Stream listpacks len loading failed.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while(listpacks--) {
|
||||
/* Get the master ID, the one we'll use as key of the radix tree
|
||||
* node: the entries inside the listpack itself are delta-encoded
|
||||
* relatively to this ID. */
|
||||
sds nodekey = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
|
||||
if (nodekey == NULL) {
|
||||
rdbReportReadError("Stream master ID loading failed: invalid encoding or I/O error.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
if (sdslen(nodekey) != sizeof(streamID)) {
|
||||
rdbExitReportCorruptRDB("Stream node key entry is not the "
|
||||
"size of a stream ID");
|
||||
@ -1653,12 +1713,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
/* Load the listpack. */
|
||||
unsigned char *lp =
|
||||
rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN,NULL);
|
||||
if (lp == NULL) return NULL;
|
||||
if (lp == NULL) {
|
||||
rdbReportReadError("Stream listpacks loading failed.");
|
||||
sdsfree(nodekey);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
unsigned char *first = lpFirst(lp);
|
||||
if (first == NULL) {
|
||||
/* Serialized listpacks should never be empty, since on
|
||||
* deletion we should remove the radix tree key if the
|
||||
* resulting listpack is emtpy. */
|
||||
* resulting listpack is empty. */
|
||||
rdbExitReportCorruptRDB("Empty listpack inside stream");
|
||||
}
|
||||
|
||||
@ -1671,12 +1736,24 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
}
|
||||
/* Load total number of items inside the stream. */
|
||||
s->length = rdbLoadLen(rdb,NULL);
|
||||
|
||||
/* Load the last entry ID. */
|
||||
s->last_id.ms = rdbLoadLen(rdb,NULL);
|
||||
s->last_id.seq = rdbLoadLen(rdb,NULL);
|
||||
|
||||
if (rioGetReadError(rdb)) {
|
||||
rdbReportReadError("Stream object metadata loading failed.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Consumer groups loading */
|
||||
size_t cgroups_count = rdbLoadLen(rdb,NULL);
|
||||
uint64_t cgroups_count = rdbLoadLen(rdb,NULL);
|
||||
if (cgroups_count == RDB_LENERR) {
|
||||
rdbReportReadError("Stream cgroup count loading failed.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
while(cgroups_count--) {
|
||||
/* Get the consumer group name and ID. We can then create the
|
||||
* consumer group ASAP and populate its structure as
|
||||
@ -1684,11 +1761,21 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
streamID cg_id;
|
||||
sds cgname = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
|
||||
if (cgname == NULL) {
|
||||
rdbExitReportCorruptRDB(
|
||||
rdbReportReadError(
|
||||
"Error reading the consumer group name from Stream");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cg_id.ms = rdbLoadLen(rdb,NULL);
|
||||
cg_id.seq = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) {
|
||||
rdbReportReadError("Stream cgroup ID loading failed.");
|
||||
sdsfree(cgname);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
streamCG *cgroup = streamCreateCG(s,cgname,sdslen(cgname),&cg_id);
|
||||
if (cgroup == NULL)
|
||||
rdbExitReportCorruptRDB("Duplicated consumer group name %s",
|
||||
@ -1700,13 +1787,28 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
* owner, since consumers for this group and their messages will
|
||||
* be read as a next step. So for now leave them not resolved
|
||||
* and later populate it. */
|
||||
size_t pel_size = rdbLoadLen(rdb,NULL);
|
||||
uint64_t pel_size = rdbLoadLen(rdb,NULL);
|
||||
if (pel_size == RDB_LENERR) {
|
||||
rdbReportReadError("Stream PEL size loading failed.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
while(pel_size--) {
|
||||
unsigned char rawid[sizeof(streamID)];
|
||||
rdbLoadRaw(rdb,rawid,sizeof(rawid));
|
||||
if (rioRead(rdb,rawid,sizeof(rawid)) == 0) {
|
||||
rdbReportReadError("Stream PEL ID loading failed.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
streamNACK *nack = streamCreateNACK(NULL);
|
||||
nack->delivery_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
nack->delivery_count = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) {
|
||||
rdbReportReadError("Stream PEL NACK loading failed.");
|
||||
decrRefCount(o);
|
||||
streamFreeNACK(nack);
|
||||
return NULL;
|
||||
}
|
||||
if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL))
|
||||
rdbExitReportCorruptRDB("Duplicated gobal PEL entry "
|
||||
"loading stream consumer group");
|
||||
@ -1714,24 +1816,47 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
|
||||
/* Now that we loaded our global PEL, we need to load the
|
||||
* consumers and their local PELs. */
|
||||
size_t consumers_num = rdbLoadLen(rdb,NULL);
|
||||
uint64_t consumers_num = rdbLoadLen(rdb,NULL);
|
||||
if (consumers_num == RDB_LENERR) {
|
||||
rdbReportReadError("Stream consumers num loading failed.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
while(consumers_num--) {
|
||||
sds cname = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
|
||||
if (cname == NULL) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Error reading the consumer name from Stream group");
|
||||
rdbReportReadError(
|
||||
"Error reading the consumer name from Stream group.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
|
||||
1);
|
||||
sdsfree(cname);
|
||||
consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
if (rioGetReadError(rdb)) {
|
||||
rdbReportReadError("Stream short read reading seen time.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Load the PEL about entries owned by this specific
|
||||
* consumer. */
|
||||
pel_size = rdbLoadLen(rdb,NULL);
|
||||
if (pel_size == RDB_LENERR) {
|
||||
rdbReportReadError(
|
||||
"Stream consumer PEL num loading failed.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
while(pel_size--) {
|
||||
unsigned char rawid[sizeof(streamID)];
|
||||
rdbLoadRaw(rdb,rawid,sizeof(rawid));
|
||||
if (rioRead(rdb,rawid,sizeof(rawid)) == 0) {
|
||||
rdbReportReadError(
|
||||
"Stream short read reading PEL streamID.");
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
streamNACK *nack = raxFind(cgroup->pel,rawid,sizeof(rawid));
|
||||
if (nack == raxNotFound)
|
||||
rdbExitReportCorruptRDB("Consumer entry not found in "
|
||||
@ -1750,6 +1875,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) return NULL;
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
|
||||
@ -1764,7 +1890,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
exit(1);
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
@ -1777,6 +1903,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
/* Module v2 serialization has an EOF mark at the end. */
|
||||
if (io.ver == 2) {
|
||||
uint64_t eof = rdbLoadLen(rdb,NULL);
|
||||
if (eof == RDB_LENERR) {
|
||||
o = createModuleObject(mt,ptr); /* creating just in order to easily destroy */
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||
serverLog(LL_WARNING,"The RDB file contains module data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||
exit(1);
|
||||
@ -1790,25 +1921,31 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
}
|
||||
o = createModuleObject(mt,ptr);
|
||||
} else {
|
||||
rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
|
||||
rdbReportReadError("Unknown RDB encoding type %d",rdbtype);
|
||||
return NULL;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Mark that we are loading in the global state and setup the fields
|
||||
* needed to provide loading stats. */
|
||||
void startLoading(FILE *fp) {
|
||||
struct stat sb;
|
||||
|
||||
void startLoading(size_t size) {
|
||||
/* Load the DB */
|
||||
server.loading = 1;
|
||||
server.loading_start_time = time(NULL);
|
||||
server.loading_loaded_bytes = 0;
|
||||
if (fstat(fileno(fp), &sb) == -1) {
|
||||
server.loading_total_bytes = 0;
|
||||
} else {
|
||||
server.loading_total_bytes = sb.st_size;
|
||||
}
|
||||
server.loading_total_bytes = size;
|
||||
}
|
||||
|
||||
/* Mark that we are loading in the global state and setup the fields
|
||||
* needed to provide loading stats.
|
||||
* 'filename' is optional and used for rdb-check on error */
|
||||
void startLoadingFile(FILE *fp, char* filename) {
|
||||
struct stat sb;
|
||||
if (fstat(fileno(fp), &sb) == -1)
|
||||
sb.st_size = 0;
|
||||
rdbFileBeingLoaded = filename;
|
||||
startLoading(sb.st_size);
|
||||
}
|
||||
|
||||
/* Refresh the loading progress info */
|
||||
@ -1821,6 +1958,7 @@ void loadingProgress(off_t pos) {
|
||||
/* Loading finished */
|
||||
void stopLoading(void) {
|
||||
server.loading = 0;
|
||||
rdbFileBeingLoaded = NULL;
|
||||
}
|
||||
|
||||
/* Track loading progress in order to serve client's from time to time
|
||||
@ -1869,7 +2007,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
/* Key-specific attributes, set by opcodes before the key type. */
|
||||
long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now = mstime();
|
||||
long long lru_clock = LRU_CLOCK();
|
||||
|
||||
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
|
||||
@ -1883,11 +2021,13 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
* load the actual type, and continue. */
|
||||
expiretime = rdbLoadTime(rdb);
|
||||
expiretime *= 1000;
|
||||
if (rioGetReadError(rdb)) goto eoferr;
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_EXPIRETIME_MS) {
|
||||
/* EXPIRETIME_MS: milliseconds precision expire times introduced
|
||||
* with RDB v3. Like EXPIRETIME but no with more precision. */
|
||||
expiretime = rdbLoadMillisecondTime(rdb,rdbver);
|
||||
if (rioGetReadError(rdb)) goto eoferr;
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_FREQ) {
|
||||
/* FREQ: LFU frequency. */
|
||||
@ -1960,6 +2100,23 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
"Can't load Lua script from RDB file! "
|
||||
"BODY: %s", auxval->ptr);
|
||||
}
|
||||
} else if (!strcasecmp(auxkey->ptr,"redis-ver")) {
|
||||
serverLog(LL_NOTICE,"Loading RDB produced by version %s",
|
||||
(char*)auxval->ptr);
|
||||
} else if (!strcasecmp(auxkey->ptr,"ctime")) {
|
||||
time_t age = time(NULL)-strtol(auxval->ptr,NULL,10);
|
||||
if (age < 0) age = 0;
|
||||
serverLog(LL_NOTICE,"RDB age %ld seconds",
|
||||
(unsigned long) age);
|
||||
} else if (!strcasecmp(auxkey->ptr,"used-mem")) {
|
||||
long long usedmem = strtoll(auxval->ptr,NULL,10);
|
||||
serverLog(LL_NOTICE,"RDB memory usage when created %.2f Mb",
|
||||
(double) usedmem / (1024*1024));
|
||||
} else if (!strcasecmp(auxkey->ptr,"aof-preamble")) {
|
||||
long long haspreamble = strtoll(auxval->ptr,NULL,10);
|
||||
if (haspreamble) serverLog(LL_NOTICE,"RDB has an AOF tail");
|
||||
} else if (!strcasecmp(auxkey->ptr,"redis-bits")) {
|
||||
/* Just ignored. */
|
||||
} else {
|
||||
/* We ignore fields we don't understand, as by AUX field
|
||||
* contract. */
|
||||
@ -1971,15 +2128,12 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
decrRefCount(auxval);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_MODULE_AUX) {
|
||||
/* This is just for compatibility with the future: we have plans
|
||||
* to add the ability for modules to store anything in the RDB
|
||||
* file, like data that is not related to the Redis key space.
|
||||
* Such data will potentially be stored both before and after the
|
||||
* RDB keys-values section. For this reason since RDB version 9,
|
||||
* we have the ability to read a MODULE_AUX opcode followed by an
|
||||
* identifier of the module, and a serialized value in "MODULE V2"
|
||||
* format. */
|
||||
/* Load module data that is not related to the Redis key space.
|
||||
* Such data can be potentially be stored both before and after the
|
||||
* RDB keys-values section. */
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
int when = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) goto eoferr;
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
@ -1989,21 +2143,44 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
|
||||
exit(1);
|
||||
} else if (!rdbCheckMode && mt != NULL) {
|
||||
/* This version of Redis actually does not know what to do
|
||||
* with modules AUX data... */
|
||||
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load for the module '%s'. Probably you want to use a newer version of Redis which implements aux data callbacks", name);
|
||||
exit(1);
|
||||
if (!mt->aux_load) {
|
||||
/* Module doesn't support AUX. */
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data, but the module '%s' doesn't seem to support it.", name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb,NULL);
|
||||
io.ver = 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
exit(1);
|
||||
}
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
uint64_t eof = rdbLoadLen(rdb,NULL);
|
||||
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||
exit(1);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
/* RDB check mode. */
|
||||
robj *aux = rdbLoadCheckModuleValue(rdb,name);
|
||||
decrRefCount(aux);
|
||||
continue; /* Read next opcode. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Read key */
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
@ -2018,7 +2195,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
||||
|
||||
|
||||
/* Set usage information (for eviction). */
|
||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock);
|
||||
|
||||
@ -2050,10 +2227,15 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
serverLog(LL_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
|
||||
rdbExitReportCorruptRDB("Unexpected EOF reading RDB file");
|
||||
return C_ERR; /* Just to avoid warning */
|
||||
/* Unexpected end of file is handled here calling rdbReportReadError():
|
||||
* this will in turn either abort Redis in most cases, or if we are loading
|
||||
* the RDB file from a socket during initial SYNC (diskless replica mode),
|
||||
* we'll report the error to the caller, so that we can retry. */
|
||||
eoferr:
|
||||
serverLog(LL_WARNING,
|
||||
"Short read or OOM loading DB. Unrecoverable error, aborting now.");
|
||||
rdbReportReadError("Unexpected EOF reading RDB file");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Like rdbLoadRio() but takes a filename instead of a rio stream. The
|
||||
@ -2069,7 +2251,7 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi) {
|
||||
int retval;
|
||||
|
||||
if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
|
||||
startLoading(fp);
|
||||
startLoadingFile(fp, filename);
|
||||
rioInitWithFile(&rdb,fp);
|
||||
retval = rdbLoadRio(&rdb,rsi,0);
|
||||
fclose(fp);
|
||||
@ -2099,7 +2281,7 @@ void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error conditon. */
|
||||
* tirggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.lastbgsave_status = C_ERR;
|
||||
}
|
||||
@ -2136,7 +2318,7 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
* in error state.
|
||||
*
|
||||
* If the process returned an error, consider the list of slaves that
|
||||
* can continue to be emtpy, so that it's just a special case of the
|
||||
* can continue to be empty, so that it's just a special case of the
|
||||
* normal code path. */
|
||||
ok_slaves = zmalloc(sizeof(uint64_t)); /* Make space for the count. */
|
||||
ok_slaves[0] = 0;
|
||||
@ -2222,6 +2404,16 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Kill the RDB saving child using SIGUSR1 (so that the parent will know
|
||||
* the child did not exit for an error, but because we wanted), and performs
|
||||
* the cleanup needed. */
|
||||
void killRDBChild(void) {
|
||||
kill(server.rdb_child_pid,SIGUSR1);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
|
||||
/* Spawn an RDB child that writes the RDB to the sockets of the slaves
|
||||
* that are currently in SLAVE_STATE_WAIT_BGSAVE_START state. */
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
|
@ -140,11 +140,12 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
robj *rdbLoadStringObject(rio *rdb);
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -33,11 +33,11 @@
|
||||
|
||||
#define ERROR(...) { \
|
||||
char __buf[1024]; \
|
||||
sprintf(__buf, __VA_ARGS__); \
|
||||
sprintf(error, "0x%16llx: %s", (long long)epos, __buf); \
|
||||
snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
|
||||
snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
|
||||
}
|
||||
|
||||
static char error[1024];
|
||||
static char error[1044];
|
||||
static off_t epos;
|
||||
|
||||
int consumeNewline(char *buf) {
|
||||
|
@ -202,7 +202,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
}
|
||||
|
||||
expiretime = -1;
|
||||
startLoading(fp);
|
||||
startLoadingFile(fp, rdbfilename);
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
|
||||
@ -216,14 +216,16 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
/* EXPIRETIME: load an expire associated with the next key
|
||||
* to load. Note that after loading an expire we need to
|
||||
* load the actual type, and continue. */
|
||||
if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
|
||||
expiretime = rdbLoadTime(&rdb);
|
||||
expiretime *= 1000;
|
||||
if (rioGetReadError(&rdb)) goto eoferr;
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_EXPIRETIME_MS) {
|
||||
/* EXPIRETIME_MS: milliseconds precision expire times introduced
|
||||
* with RDB v3. Like EXPIRETIME but no with more precision. */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
|
||||
if ((expiretime = rdbLoadMillisecondTime(&rdb, rdbver)) == -1) goto eoferr;
|
||||
expiretime = rdbLoadMillisecondTime(&rdb, rdbver);
|
||||
if (rioGetReadError(&rdb)) goto eoferr;
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_FREQ) {
|
||||
/* FREQ: LFU frequency. */
|
||||
@ -285,13 +287,9 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (server.masterhost == NULL && expiretime != -1 && expiretime < now)
|
||||
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
if (expiretime != -1) rdbstate.expires++;
|
||||
rdbstate.key = NULL;
|
||||
@ -318,6 +316,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
}
|
||||
|
||||
if (closefile) fclose(fp);
|
||||
stopLoading();
|
||||
return 0;
|
||||
|
||||
eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
@ -328,6 +327,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
}
|
||||
err:
|
||||
if (closefile) fclose(fp);
|
||||
stopLoading();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
2418
src/redis-cli.c
2418
src/redis-cli.c
File diff suppressed because it is too large
Load Diff
1913
src/redis-trib.rb
1913
src/redis-trib.rb
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
/* redisassert.h -- Drop in replacemnet assert.h that prints the stack trace
|
||||
/* redisassert.h -- Drop in replacements assert.h that prints the stack trace
|
||||
* in the Redis logs.
|
||||
*
|
||||
* This file should be included instead of "assert.h" inside libraries used by
|
||||
|
@ -85,6 +85,11 @@
|
||||
#define REDISMODULE_CTX_FLAGS_OOM (1<<10)
|
||||
/* Less than 25% of memory available according to maxmemory. */
|
||||
#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 */
|
||||
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
|
||||
@ -95,7 +100,8 @@
|
||||
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
|
||||
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_KEY_MISS) /* A */
|
||||
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
@ -117,14 +123,27 @@
|
||||
#define REDISMODULE_NODE_FAIL (1<<4)
|
||||
#define REDISMODULE_NODE_NOFAILOVER (1<<5)
|
||||
|
||||
#define REDISMODULE_CLUSTER_FLAG_NONE 0
|
||||
#define REDISMODULE_CLUSTER_FLAG_NO_FAILOVER (1<<1)
|
||||
#define REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION (1<<2)
|
||||
|
||||
#define REDISMODULE_NOT_USED(V) ((void) V)
|
||||
|
||||
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
|
||||
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||
|
||||
/* This type represents a timer handle, and is returned when a timer is
|
||||
* registered and used in order to invalidate a timer. It's just a 64 bit
|
||||
* number, because this is how each timer is represented inside the radix tree
|
||||
* of timers that are going to expire, sorted by expire time. */
|
||||
typedef uint64_t RedisModuleTimerID;
|
||||
|
||||
/* CommandFilter Flags */
|
||||
|
||||
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
||||
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
|
||||
|
||||
/* ------------------------- End of common defines ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
@ -141,20 +160,27 @@ typedef struct RedisModuleType RedisModuleType;
|
||||
typedef struct RedisModuleDigest RedisModuleDigest;
|
||||
typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
typedef struct RedisModuleClusterInfo RedisModuleClusterInfo;
|
||||
typedef struct RedisModuleDict RedisModuleDict;
|
||||
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||
typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||
typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int when);
|
||||
typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when);
|
||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
|
||||
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
|
||||
typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
||||
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
||||
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 1
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||
typedef struct RedisModuleTypeMethods {
|
||||
uint64_t version;
|
||||
RedisModuleTypeLoadFunc rdb_load;
|
||||
@ -163,6 +189,9 @@ typedef struct RedisModuleTypeMethods {
|
||||
RedisModuleTypeMemUsageFunc mem_usage;
|
||||
RedisModuleTypeDigestFunc digest;
|
||||
RedisModuleTypeFreeFunc free;
|
||||
RedisModuleTypeAuxLoadFunc aux_load;
|
||||
RedisModuleTypeAuxSaveFunc aux_save;
|
||||
int aux_save_triggers;
|
||||
} RedisModuleTypeMethods;
|
||||
|
||||
#define REDISMODULE_GET_API(name) \
|
||||
@ -208,6 +237,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);
|
||||
@ -269,10 +299,33 @@ int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, Re
|
||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md);
|
||||
RedisModuleDict *REDISMODULE_API_FUNC(RedisModule_CreateDict)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d);
|
||||
uint64_t REDISMODULE_API_FUNC(RedisModule_DictSize)(RedisModuleDict *d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval);
|
||||
RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen);
|
||||
RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DictIteratorStop)(RedisModuleDictIter *di);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
||||
|
||||
/* Experimental APIs */
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
@ -303,6 +356,16 @@ size_t REDISMODULE_API_FUNC(RedisModule_GetClusterSize)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname);
|
||||
RedisModuleCommandFilter *REDISMODULE_API_FUNC(RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
#endif
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
@ -325,6 +388,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);
|
||||
@ -408,10 +472,33 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(RetainString);
|
||||
REDISMODULE_GET_API(StringCompare);
|
||||
REDISMODULE_GET_API(GetContextFromIO);
|
||||
REDISMODULE_GET_API(GetKeyNameFromIO);
|
||||
REDISMODULE_GET_API(Milliseconds);
|
||||
REDISMODULE_GET_API(DigestAddStringBuffer);
|
||||
REDISMODULE_GET_API(DigestAddLongLong);
|
||||
REDISMODULE_GET_API(DigestEndSequence);
|
||||
REDISMODULE_GET_API(CreateDict);
|
||||
REDISMODULE_GET_API(FreeDict);
|
||||
REDISMODULE_GET_API(DictSize);
|
||||
REDISMODULE_GET_API(DictSetC);
|
||||
REDISMODULE_GET_API(DictReplaceC);
|
||||
REDISMODULE_GET_API(DictSet);
|
||||
REDISMODULE_GET_API(DictReplace);
|
||||
REDISMODULE_GET_API(DictGetC);
|
||||
REDISMODULE_GET_API(DictGet);
|
||||
REDISMODULE_GET_API(DictDelC);
|
||||
REDISMODULE_GET_API(DictDel);
|
||||
REDISMODULE_GET_API(DictIteratorStartC);
|
||||
REDISMODULE_GET_API(DictIteratorStart);
|
||||
REDISMODULE_GET_API(DictIteratorStop);
|
||||
REDISMODULE_GET_API(DictIteratorReseekC);
|
||||
REDISMODULE_GET_API(DictIteratorReseek);
|
||||
REDISMODULE_GET_API(DictNextC);
|
||||
REDISMODULE_GET_API(DictPrevC);
|
||||
REDISMODULE_GET_API(DictNext);
|
||||
REDISMODULE_GET_API(DictPrev);
|
||||
REDISMODULE_GET_API(DictCompare);
|
||||
REDISMODULE_GET_API(DictCompareC);
|
||||
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||
@ -440,6 +527,16 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(GetClusterSize);
|
||||
REDISMODULE_GET_API(GetRandomBytes);
|
||||
REDISMODULE_GET_API(GetRandomHexChars);
|
||||
REDISMODULE_GET_API(SetClusterFlags);
|
||||
REDISMODULE_GET_API(ExportSharedAPI);
|
||||
REDISMODULE_GET_API(GetSharedAPI);
|
||||
REDISMODULE_GET_API(RegisterCommandFilter);
|
||||
REDISMODULE_GET_API(UnregisterCommandFilter);
|
||||
REDISMODULE_GET_API(CommandFilterArgsCount);
|
||||
REDISMODULE_GET_API(CommandFilterArgGet);
|
||||
REDISMODULE_GET_API(CommandFilterArgInsert);
|
||||
REDISMODULE_GET_API(CommandFilterArgReplace);
|
||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
|
File diff suppressed because it is too large
Load Diff
124
src/rio.c
124
src/rio.c
@ -92,6 +92,7 @@ static const rio rioBufferIO = {
|
||||
rioBufferFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* flags */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
@ -145,6 +146,7 @@ static const rio rioFileIO = {
|
||||
rioFileFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* flags */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
@ -157,7 +159,124 @@ void rioInitWithFile(rio *r, FILE *fp) {
|
||||
r->io.file.autosync = 0;
|
||||
}
|
||||
|
||||
/* ------------------- File descriptors set implementation ------------------- */
|
||||
/* ------------------- File descriptor implementation -------------------
|
||||
* We use this RIO implemetnation when reading an RDB file directly from
|
||||
* the socket to the memory via rdbLoadRio(), thus this implementation
|
||||
* only implements reading from a file descriptor that is, normally,
|
||||
* just a socket. */
|
||||
|
||||
static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
|
||||
UNUSED(r);
|
||||
UNUSED(buf);
|
||||
UNUSED(len);
|
||||
return 0; /* Error, this target does not yet support writing. */
|
||||
}
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFdRead(rio *r, void *buf, size_t len) {
|
||||
size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos;
|
||||
|
||||
/* If the buffer is too small for the entire request: realloc. */
|
||||
if (sdslen(r->io.fd.buf) + sdsavail(r->io.fd.buf) < len)
|
||||
r->io.fd.buf = sdsMakeRoomFor(r->io.fd.buf, len - sdslen(r->io.fd.buf));
|
||||
|
||||
/* If the remaining unused buffer is not large enough: memmove so that we
|
||||
* can read the rest. */
|
||||
if (len > avail && sdsavail(r->io.fd.buf) < len - avail) {
|
||||
sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
|
||||
r->io.fd.pos = 0;
|
||||
}
|
||||
|
||||
/* If we don't already have all the data in the sds, read more */
|
||||
while (len > sdslen(r->io.fd.buf) - r->io.fd.pos) {
|
||||
size_t buffered = sdslen(r->io.fd.buf) - r->io.fd.pos;
|
||||
size_t toread = len - buffered;
|
||||
/* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
|
||||
* the two. */
|
||||
if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN;
|
||||
if (toread > sdsavail(r->io.fd.buf)) toread = sdsavail(r->io.fd.buf);
|
||||
if (r->io.fd.read_limit != 0 &&
|
||||
r->io.fd.read_so_far + buffered + toread > r->io.fd.read_limit)
|
||||
{
|
||||
if (r->io.fd.read_limit >= r->io.fd.read_so_far - buffered)
|
||||
toread = r->io.fd.read_limit - r->io.fd.read_so_far - buffered;
|
||||
else {
|
||||
errno = EOVERFLOW;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int retval = read(r->io.fd.fd,
|
||||
(char*)r->io.fd.buf + sdslen(r->io.fd.buf),
|
||||
toread);
|
||||
if (retval <= 0) {
|
||||
if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
||||
return 0;
|
||||
}
|
||||
sdsIncrLen(r->io.fd.buf, retval);
|
||||
}
|
||||
|
||||
memcpy(buf, (char*)r->io.fd.buf + r->io.fd.pos, len);
|
||||
r->io.fd.read_so_far += len;
|
||||
r->io.fd.pos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Returns read/write position in file. */
|
||||
static off_t rioFdTell(rio *r) {
|
||||
return r->io.fd.read_so_far;
|
||||
}
|
||||
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioFdFlush(rio *r) {
|
||||
/* Our flush is implemented by the write method, that recognizes a
|
||||
* buffer set to NULL with a count of zero as a flush request. */
|
||||
return rioFdWrite(r,NULL,0);
|
||||
}
|
||||
|
||||
static const rio rioFdIO = {
|
||||
rioFdRead,
|
||||
rioFdWrite,
|
||||
rioFdTell,
|
||||
rioFdFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* flags */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
/* Create an RIO that implements a buffered read from an fd
|
||||
* read_limit argument stops buffering when the reaching the limit. */
|
||||
void rioInitWithFd(rio *r, int fd, size_t read_limit) {
|
||||
*r = rioFdIO;
|
||||
r->io.fd.fd = fd;
|
||||
r->io.fd.pos = 0;
|
||||
r->io.fd.read_limit = read_limit;
|
||||
r->io.fd.read_so_far = 0;
|
||||
r->io.fd.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
|
||||
sdsclear(r->io.fd.buf);
|
||||
}
|
||||
|
||||
/* Release the RIO tream. Optionally returns the unread buffered data
|
||||
* when the SDS pointer 'remaining' is passed. */
|
||||
void rioFreeFd(rio *r, sds *remaining) {
|
||||
if (remaining && (size_t)r->io.fd.pos < sdslen(r->io.fd.buf)) {
|
||||
if (r->io.fd.pos > 0) sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
|
||||
*remaining = r->io.fd.buf;
|
||||
} else {
|
||||
sdsfree(r->io.fd.buf);
|
||||
if (remaining) *remaining = NULL;
|
||||
}
|
||||
r->io.fd.buf = NULL;
|
||||
}
|
||||
|
||||
/* ------------------- File descriptors set implementation ------------------
|
||||
* This target is used to write the RDB file to N different replicas via
|
||||
* sockets, when the master just streams the data to the replicas without
|
||||
* creating an RDB on-disk image (diskless replication option).
|
||||
* It only implements writes. */
|
||||
|
||||
/* Returns 1 or 0 for success/failure.
|
||||
* The function returns success as long as we are able to correctly write
|
||||
@ -258,6 +377,7 @@ static const rio rioFdsetIO = {
|
||||
rioFdsetFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* flags */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
@ -300,7 +420,7 @@ void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) {
|
||||
* disk I/O concentrated in very little time. When we fsync in an explicit
|
||||
* way instead the I/O pressure is more distributed across time. */
|
||||
void rioSetAutoSync(rio *r, off_t bytes) {
|
||||
serverAssert(r->read == rioFileIO.read);
|
||||
if(r->write != rioFileIO.write) return;
|
||||
r->io.file.autosync = bytes;
|
||||
}
|
||||
|
||||
|
45
src/rio.h
45
src/rio.h
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2009-2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -36,6 +36,9 @@
|
||||
#include <stdint.h>
|
||||
#include "sds.h"
|
||||
|
||||
#define RIO_FLAG_READ_ERROR (1<<0)
|
||||
#define RIO_FLAG_WRITE_ERROR (1<<1)
|
||||
|
||||
struct _rio {
|
||||
/* Backend functions.
|
||||
* Since this functions do not tolerate short writes or reads the return
|
||||
@ -51,8 +54,8 @@ struct _rio {
|
||||
* computation. */
|
||||
void (*update_cksum)(struct _rio *, const void *buf, size_t len);
|
||||
|
||||
/* The current checksum */
|
||||
uint64_t cksum;
|
||||
/* The current checksum and flags (see RIO_FLAG_*) */
|
||||
uint64_t cksum, flags;
|
||||
|
||||
/* number of bytes read or written */
|
||||
size_t processed_bytes;
|
||||
@ -73,6 +76,14 @@ struct _rio {
|
||||
off_t buffered; /* Bytes written since last fsync. */
|
||||
off_t autosync; /* fsync after 'autosync' bytes written. */
|
||||
} file;
|
||||
/* file descriptor */
|
||||
struct {
|
||||
int fd; /* File descriptor. */
|
||||
off_t pos; /* pos in buf that was returned */
|
||||
sds buf; /* buffered data */
|
||||
size_t read_limit; /* don't allow to buffer/read more than that */
|
||||
size_t read_so_far; /* amount of data read from the rio (not buffered) */
|
||||
} fd;
|
||||
/* Multiple FDs target (used to write to N sockets). */
|
||||
struct {
|
||||
int *fds; /* File descriptors. */
|
||||
@ -91,11 +102,14 @@ typedef struct _rio rio;
|
||||
* if needed. */
|
||||
|
||||
static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
|
||||
if (r->flags & RIO_FLAG_WRITE_ERROR) return 0;
|
||||
while (len) {
|
||||
size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
|
||||
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write);
|
||||
if (r->write(r,buf,bytes_to_write) == 0)
|
||||
if (r->write(r,buf,bytes_to_write) == 0) {
|
||||
r->flags |= RIO_FLAG_WRITE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
buf = (char*)buf + bytes_to_write;
|
||||
len -= bytes_to_write;
|
||||
r->processed_bytes += bytes_to_write;
|
||||
@ -104,10 +118,13 @@ static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
|
||||
}
|
||||
|
||||
static inline size_t rioRead(rio *r, void *buf, size_t len) {
|
||||
if (r->flags & RIO_FLAG_READ_ERROR) return 0;
|
||||
while (len) {
|
||||
size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
|
||||
if (r->read(r,buf,bytes_to_read) == 0)
|
||||
if (r->read(r,buf,bytes_to_read) == 0) {
|
||||
r->flags |= RIO_FLAG_READ_ERROR;
|
||||
return 0;
|
||||
}
|
||||
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read);
|
||||
buf = (char*)buf + bytes_to_read;
|
||||
len -= bytes_to_read;
|
||||
@ -124,11 +141,29 @@ static inline int rioFlush(rio *r) {
|
||||
return r->flush(r);
|
||||
}
|
||||
|
||||
/* This function allows to know if there was a read error in any past
|
||||
* operation, since the rio stream was created or since the last call
|
||||
* to rioClearError(). */
|
||||
static inline int rioGetReadError(rio *r) {
|
||||
return (r->flags & RIO_FLAG_READ_ERROR) != 0;
|
||||
}
|
||||
|
||||
/* Like rioGetReadError() but for write errors. */
|
||||
static inline int rioGetWriteError(rio *r) {
|
||||
return (r->flags & RIO_FLAG_READ_ERROR) != 0;
|
||||
}
|
||||
|
||||
static inline void rioClearErrors(rio *r) {
|
||||
r->flags &= ~(RIO_FLAG_READ_ERROR|RIO_FLAG_WRITE_ERROR);
|
||||
}
|
||||
|
||||
void rioInitWithFile(rio *r, FILE *fp);
|
||||
void rioInitWithBuffer(rio *r, sds s);
|
||||
void rioInitWithFd(rio *r, int fd, size_t read_limit);
|
||||
void rioInitWithFdset(rio *r, int *fds, int numfds);
|
||||
|
||||
void rioFreeFdset(rio *r);
|
||||
void rioFreeFd(rio *r, sds* out_remainingBufferedData);
|
||||
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, long count);
|
||||
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
|
||||
|
167
src/scripting.c
167
src/scripting.c
@ -42,7 +42,7 @@ char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype);
|
||||
int redis_math_random (lua_State *L);
|
||||
int redis_math_randomseed (lua_State *L);
|
||||
void ldbInit(void);
|
||||
@ -132,7 +132,9 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
||||
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
|
||||
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
|
||||
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
|
||||
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply); break;
|
||||
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
case '%': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
case '~': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@ -180,22 +182,38 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) {
|
||||
return p+2;
|
||||
}
|
||||
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply) {
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
||||
char *p = strchr(reply+1,'\r');
|
||||
long long mbulklen;
|
||||
int j = 0;
|
||||
|
||||
string2ll(reply+1,p-reply-1,&mbulklen);
|
||||
p += 2;
|
||||
if (mbulklen == -1) {
|
||||
lua_pushboolean(lua,0);
|
||||
return p;
|
||||
}
|
||||
lua_newtable(lua);
|
||||
for (j = 0; j < mbulklen; j++) {
|
||||
lua_pushnumber(lua,j+1);
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
lua_settable(lua,-3);
|
||||
if (server.lua_caller->resp == 2 || atype == '*') {
|
||||
p += 2;
|
||||
if (mbulklen == -1) {
|
||||
lua_pushboolean(lua,0);
|
||||
return p;
|
||||
}
|
||||
lua_newtable(lua);
|
||||
for (j = 0; j < mbulklen; j++) {
|
||||
lua_pushnumber(lua,j+1);
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
lua_settable(lua,-3);
|
||||
}
|
||||
} else if (server.lua_caller->resp == 3) {
|
||||
/* Here we handle only Set and Map replies in RESP3 mode, since arrays
|
||||
* follow the above RESP2 code path. */
|
||||
p += 2;
|
||||
lua_newtable(lua);
|
||||
for (j = 0; j < mbulklen; j++) {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
if (atype == '%') {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
} else {
|
||||
lua_pushboolean(lua,1);
|
||||
}
|
||||
lua_settable(lua,-3);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
@ -282,7 +300,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.nullbulk);
|
||||
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.null[c->resp]);
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
|
||||
@ -315,7 +333,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
sdsfree(ok);
|
||||
lua_pop(lua,1);
|
||||
} else {
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int j = 1, mbulklen = 0;
|
||||
|
||||
lua_pop(lua,1); /* Discard the 'ok' field value we popped */
|
||||
@ -330,11 +348,11 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
luaReplyToRedisReply(c, lua);
|
||||
mbulklen++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,mbulklen);
|
||||
setDeferredArrayLen(c,replylen,mbulklen);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
}
|
||||
@ -442,6 +460,12 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
/* Setup our fake client for command execution */
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
c->user = server.lua_caller->user;
|
||||
|
||||
/* Process module hooks */
|
||||
moduleCallCommandFilters(c);
|
||||
argv = c->argv;
|
||||
argc = c->argc;
|
||||
|
||||
/* Log the command if debugging is active. */
|
||||
if (ldb.active && ldb.step) {
|
||||
@ -479,10 +503,24 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Check the ACLs. */
|
||||
int acl_retval = ACLCheckCommandPerm(c);
|
||||
if (acl_retval != ACL_OK) {
|
||||
if (acl_retval == ACL_DENIED_CMD)
|
||||
luaPushError(lua, "The user executing the script can't run this "
|
||||
"command or subcommand");
|
||||
else
|
||||
luaPushError(lua, "The user executing the script can't access "
|
||||
"at least one of the keys mentioned in the "
|
||||
"command arguments");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Write commands are forbidden against read-only slaves, or if a
|
||||
* command marked as non-deterministic was already called in the context
|
||||
* of this script. */
|
||||
if (cmd->flags & CMD_WRITE) {
|
||||
int deny_write_type = writeCommandsDeniedByDiskError();
|
||||
if (server.lua_random_dirty && !server.lua_replicate_commands) {
|
||||
luaPushError(lua,
|
||||
"Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.");
|
||||
@ -493,11 +531,16 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
{
|
||||
luaPushError(lua, shared.roslaveerr->ptr);
|
||||
goto cleanup;
|
||||
} else if (server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == C_ERR)
|
||||
{
|
||||
luaPushError(lua, shared.bgsaveerr->ptr);
|
||||
} else if (deny_write_type != DISK_ERROR_TYPE_NONE) {
|
||||
if (deny_write_type == DISK_ERROR_TYPE_RDB) {
|
||||
luaPushError(lua, shared.bgsaveerr->ptr);
|
||||
} else {
|
||||
sds aof_write_err = sdscatfmt(sdsempty(),
|
||||
"-MISCONF Errors writing to the AOF file: %s\r\n",
|
||||
strerror(server.aof_last_write_errno));
|
||||
luaPushError(lua, aof_write_err);
|
||||
sdsfree(aof_write_err);
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
@ -506,10 +549,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* could enlarge the memory usage are not allowed, but only if this is the
|
||||
* first write in the context of this script, otherwise we can't stop
|
||||
* in the middle. */
|
||||
if (server.maxmemory && server.lua_write_dirty == 0 &&
|
||||
if (server.maxmemory && /* Maxmemory is actually enabled. */
|
||||
!server.loading && /* Don't care about mem if loading. */
|
||||
!server.masterhost && /* Slave must execute the script. */
|
||||
server.lua_write_dirty == 0 && /* Script had no side effects so far. */
|
||||
(cmd->flags & CMD_DENYOOM))
|
||||
{
|
||||
if (freeMemoryIfNeeded() == C_ERR) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
|
||||
luaPushError(lua, shared.oomerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
@ -575,9 +621,9 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
reply = sdsnewlen(c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
while(listLength(c->reply)) {
|
||||
sds o = listNodeValue(listFirst(c->reply));
|
||||
clientReplyBlock *o = listNodeValue(listFirst(c->reply));
|
||||
|
||||
reply = sdscatsds(reply,o);
|
||||
reply = sdscatlen(reply,o->buf,o->used);
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
}
|
||||
@ -628,6 +674,8 @@ cleanup:
|
||||
argv_size = 0;
|
||||
}
|
||||
|
||||
c->user = NULL;
|
||||
|
||||
if (raise_error) {
|
||||
/* If we are here we should have an error in the stack, in the
|
||||
* form of a table with an "err" field. Extract the string to
|
||||
@ -768,7 +816,7 @@ int luaRedisSetReplCommand(lua_State *lua) {
|
||||
|
||||
flags = lua_tonumber(lua,-1);
|
||||
if ((flags & ~(PROPAGATE_AOF|PROPAGATE_REPL)) != 0) {
|
||||
lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_SLAVE, REPL_ALL or REPL_NONE.");
|
||||
lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_REPLICA, REPL_ALL or REPL_NONE.");
|
||||
return lua_error(lua);
|
||||
}
|
||||
server.lua_repl = flags;
|
||||
@ -908,7 +956,6 @@ void scriptingInit(int setup) {
|
||||
server.lua_client = NULL;
|
||||
server.lua_caller = NULL;
|
||||
server.lua_timedout = 0;
|
||||
server.lua_always_replicate_commands = 0; /* Only DEBUG can change it.*/
|
||||
ldbInit();
|
||||
}
|
||||
|
||||
@ -919,6 +966,7 @@ void scriptingInit(int setup) {
|
||||
* This is useful for replication, as we need to replicate EVALSHA
|
||||
* as EVAL, so we need to remember the associated script. */
|
||||
server.lua_scripts = dictCreate(&shaScriptObjectDictType,NULL);
|
||||
server.lua_scripts_mem = 0;
|
||||
|
||||
/* Register the redis commands table and fields */
|
||||
lua_newtable(lua);
|
||||
@ -989,6 +1037,10 @@ void scriptingInit(int setup) {
|
||||
lua_pushnumber(lua,PROPAGATE_REPL);
|
||||
lua_settable(lua,-3);
|
||||
|
||||
lua_pushstring(lua,"REPL_REPLICA");
|
||||
lua_pushnumber(lua,PROPAGATE_REPL);
|
||||
lua_settable(lua,-3);
|
||||
|
||||
lua_pushstring(lua,"REPL_ALL");
|
||||
lua_pushnumber(lua,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
lua_settable(lua,-3);
|
||||
@ -1073,6 +1125,7 @@ void scriptingInit(int setup) {
|
||||
* This function is used in order to reset the scripting environment. */
|
||||
void scriptingRelease(void) {
|
||||
dictRelease(server.lua_scripts);
|
||||
server.lua_scripts_mem = 0;
|
||||
lua_close(server.lua);
|
||||
}
|
||||
|
||||
@ -1207,17 +1260,19 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
|
||||
* EVALSHA commands as EVAL using the original script. */
|
||||
int retval = dictAdd(server.lua_scripts,sha,body);
|
||||
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
|
||||
server.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
|
||||
incrRefCount(body);
|
||||
return sha;
|
||||
}
|
||||
|
||||
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
|
||||
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
long long elapsed;
|
||||
long long elapsed = mstime() - server.lua_time_start;
|
||||
UNUSED(ar);
|
||||
UNUSED(lua);
|
||||
|
||||
elapsed = mstime() - server.lua_time_start;
|
||||
/* Set the timeout condition if not already set and the maximum
|
||||
* execution time was reached. */
|
||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
||||
server.lua_timedout = 1;
|
||||
@ -1226,7 +1281,7 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
* we need to mask the client executing the script from the event loop.
|
||||
* If we don't do that the client may disconnect and could no longer be
|
||||
* here when the EVAL command will return. */
|
||||
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
|
||||
protectClient(server.lua_caller);
|
||||
}
|
||||
if (server.lua_timedout) processEventsWhileBlocked();
|
||||
if (server.lua_kill) {
|
||||
@ -1240,6 +1295,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
lua_State *lua = server.lua;
|
||||
char funcname[43];
|
||||
long long numkeys;
|
||||
long long initial_server_dirty = server.dirty;
|
||||
int delhook = 0, err;
|
||||
|
||||
/* When we replicate whole scripts, we want the same PRNG sequence at
|
||||
@ -1336,9 +1392,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
server.lua_caller = c;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && server.masterhost == NULL &&
|
||||
ldb.active == 0)
|
||||
{
|
||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
delhook = 1;
|
||||
} else if (ldb.active) {
|
||||
@ -1355,10 +1409,11 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
if (delhook) lua_sethook(lua,NULL,0,0); /* Disable hook */
|
||||
if (server.lua_timedout) {
|
||||
server.lua_timedout = 0;
|
||||
/* Restore the readable handler that was unregistered when the
|
||||
* script timeout was detected. */
|
||||
aeCreateFileEvent(server.el,c->fd,AE_READABLE,
|
||||
readQueryFromClient,c);
|
||||
/* Restore the client that was protected when the script timeout
|
||||
* was detected. */
|
||||
unprotectClient(c);
|
||||
if (server.masterhost && server.master)
|
||||
queueClientForReprocessing(server.master);
|
||||
}
|
||||
server.lua_caller = NULL;
|
||||
|
||||
@ -1422,9 +1477,21 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
|
||||
replicationScriptCacheAdd(c->argv[1]->ptr);
|
||||
serverAssertWithInfo(c,NULL,script != NULL);
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
|
||||
/* If the script did not produce any changes in the dataset we want
|
||||
* just to replicate it as SCRIPT LOAD, otherwise we risk running
|
||||
* an aborted script on slaves (that may then produce results there)
|
||||
* or just running a CPU costly read-only script on the slaves. */
|
||||
if (server.dirty == initial_server_dirty) {
|
||||
rewriteClientCommandVector(c,3,
|
||||
resetRefCount(createStringObject("SCRIPT",6)),
|
||||
resetRefCount(createStringObject("LOAD",4)),
|
||||
script);
|
||||
} else {
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
}
|
||||
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
|
||||
}
|
||||
}
|
||||
@ -1457,11 +1524,11 @@ void evalShaCommand(client *c) {
|
||||
void scriptCommand(client *c) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
"debug (yes|sync|no) -- Set the debug mode for subsequent scripts executed.",
|
||||
"exists <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.",
|
||||
"flush -- Flush the Lua scripts cache. Very dangerous on slaves.",
|
||||
"kill -- Kill the currently executing Lua script.",
|
||||
"load <script> -- Load a script into the scripts cache, without executing it.",
|
||||
"DEBUG (yes|sync|no) -- Set the debug mode for subsequent scripts executed.",
|
||||
"EXISTS <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.",
|
||||
"FLUSH -- Flush the Lua scripts cache. Very dangerous on replicas.",
|
||||
"KILL -- Kill the currently executing Lua script.",
|
||||
"LOAD <script> -- Load a script into the scripts cache, without executing it.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -1473,7 +1540,7 @@ NULL
|
||||
} else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists")) {
|
||||
int j;
|
||||
|
||||
addReplyMultiBulkLen(c, c->argc-2);
|
||||
addReplyArrayLen(c, c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (dictFind(server.lua_scripts,c->argv[j]->ptr))
|
||||
addReply(c,shared.cone);
|
||||
@ -1488,6 +1555,8 @@ NULL
|
||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||
if (server.lua_caller == NULL) {
|
||||
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
|
||||
} else if (server.lua_caller->flags & CLIENT_MASTER) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.\r\n"));
|
||||
} else if (server.lua_write_dirty) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n"));
|
||||
} else {
|
||||
@ -1514,7 +1583,7 @@ NULL
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try SCRIPT HELP", (char*)c->argv[1]->ptr);
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1716,7 +1785,7 @@ int ldbRemoveChild(pid_t pid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the number of children we still did not received termination
|
||||
/* Return the number of children we still did not receive termination
|
||||
* acknowledge via wait() in the parent process. */
|
||||
int ldbPendingChildren(void) {
|
||||
return listLength(ldb.children);
|
||||
|
12
src/sds.c
12
src/sds.c
@ -67,8 +67,10 @@ static inline char sdsReqType(size_t string_size) {
|
||||
#if (LONG_MAX == LLONG_MAX)
|
||||
if (string_size < 1ll<<32)
|
||||
return SDS_TYPE_32;
|
||||
#endif
|
||||
return SDS_TYPE_64;
|
||||
#else
|
||||
return SDS_TYPE_32;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Create a new sds string with the content specified by the 'init' pointer
|
||||
@ -255,8 +257,12 @@ sds sdsRemoveFreeSpace(sds s) {
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
|
||||
size_t len = sdslen(s);
|
||||
size_t avail = sdsavail(s);
|
||||
sh = (char*)s-oldhdrlen;
|
||||
|
||||
/* Return ASAP if there is no space left. */
|
||||
if (avail == 0) return s;
|
||||
|
||||
/* Check what would be the minimum SDS header that is just good enough to
|
||||
* fit this string. */
|
||||
type = sdsReqType(len);
|
||||
@ -283,7 +289,7 @@ sds sdsRemoveFreeSpace(sds s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return the total size of the allocation of the specifed sds string,
|
||||
/* Return the total size of the allocation of the specified sds string,
|
||||
* including:
|
||||
* 1) The sds header before the pointer.
|
||||
* 2) The string.
|
||||
@ -693,7 +699,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
* s = sdstrim(s,"Aa. :");
|
||||
* printf("%s\n", s);
|
||||
*
|
||||
* Output will be just "Hello World".
|
||||
* Output will be just "HelloWorld".
|
||||
*/
|
||||
sds sdstrim(sds s, const char *cset) {
|
||||
char *start, *end, *sp, *ep;
|
||||
|
301
src/sentinel.c
301
src/sentinel.c
@ -178,6 +178,10 @@ typedef struct sentinelRedisInstance {
|
||||
mstime_t o_down_since_time; /* Objectively down since time. */
|
||||
mstime_t down_after_period; /* Consider it down after that period. */
|
||||
mstime_t info_refresh; /* Time at which we received INFO output from it. */
|
||||
dict *renamed_commands; /* Commands renamed in this instance:
|
||||
Sentinel will use the alternative commands
|
||||
mapped on this table to send things like
|
||||
SLAVEOF, CONFING, INFO, ... */
|
||||
|
||||
/* Role and the first time we observed it.
|
||||
* This is useful in order to delay replacing what the instance reports
|
||||
@ -383,7 +387,9 @@ void sentinelSimFailureCrash(void);
|
||||
/* ========================= Dictionary types =============================== */
|
||||
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
uint64_t dictSdsCaseHash(const void *key);
|
||||
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
|
||||
int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2);
|
||||
void releaseSentinelRedisInstance(sentinelRedisInstance *ri);
|
||||
|
||||
void dictInstancesValDestructor (void *privdata, void *obj) {
|
||||
@ -417,6 +423,16 @@ dictType leaderVotesDictType = {
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* Instance renamed commands table. */
|
||||
dictType renamedCommandsDictType = {
|
||||
dictSdsCaseHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCaseCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
dictSdsDestructor /* val destructor */
|
||||
};
|
||||
|
||||
/* =========================== Initialization =============================== */
|
||||
|
||||
void sentinelCommand(client *c);
|
||||
@ -434,15 +450,18 @@ struct redisCommand sentinelcmds[] = {
|
||||
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
|
||||
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
|
||||
{"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
|
||||
{"hello",helloCommand,-2,"no-script fast",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/* This function overwrites a few normal Redis config default with Sentinel
|
||||
* specific defaults. */
|
||||
void initSentinelConfig(void) {
|
||||
server.port = REDIS_SENTINEL_PORT;
|
||||
server.protected_mode = 0; /* Sentinel must be exposed. */
|
||||
}
|
||||
|
||||
/* Perform the Sentinel mode initialization. */
|
||||
@ -458,6 +477,11 @@ void initSentinel(void) {
|
||||
|
||||
retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
|
||||
serverAssert(retval == DICT_OK);
|
||||
|
||||
/* Translate the command string flags description into an actual
|
||||
* set of flags. */
|
||||
if (populateCommandTableParseFlags(cmd,cmd->sflags) == C_ERR)
|
||||
serverPanic("Unsupported command flag");
|
||||
}
|
||||
|
||||
/* Initialize various data structures. */
|
||||
@ -498,7 +522,7 @@ void sentinelIsRunning(void) {
|
||||
if (sentinel.myid[j] != 0) break;
|
||||
|
||||
if (j == CONFIG_RUN_ID_SIZE) {
|
||||
/* Pick ID and presist the config. */
|
||||
/* Pick ID and persist the config. */
|
||||
getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
|
||||
sentinelFlushConfig();
|
||||
}
|
||||
@ -867,17 +891,17 @@ void sentinelPendingScriptsCommand(client *c) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
addReplyMultiBulkLen(c,listLength(sentinel.scripts_queue));
|
||||
addReplyArrayLen(c,listLength(sentinel.scripts_queue));
|
||||
listRewind(sentinel.scripts_queue,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sentinelScriptJob *sj = ln->value;
|
||||
int j = 0;
|
||||
|
||||
addReplyMultiBulkLen(c,10);
|
||||
addReplyMapLen(c,5);
|
||||
|
||||
addReplyBulkCString(c,"argv");
|
||||
while (sj->argv[j]) j++;
|
||||
addReplyMultiBulkLen(c,j);
|
||||
addReplyArrayLen(c,j);
|
||||
j = 0;
|
||||
while (sj->argv[j]) addReplyBulkCString(c,sj->argv[j++]);
|
||||
|
||||
@ -1211,6 +1235,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
ri->master = master;
|
||||
ri->slaves = dictCreate(&instancesDictType,NULL);
|
||||
ri->info_refresh = 0;
|
||||
ri->renamed_commands = dictCreate(&renamedCommandsDictType,NULL);
|
||||
|
||||
/* Failover state. */
|
||||
ri->leader = NULL;
|
||||
@ -1258,6 +1283,7 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {
|
||||
sdsfree(ri->auth_pass);
|
||||
sdsfree(ri->info);
|
||||
releaseSentinelAddr(ri->addr);
|
||||
dictRelease(ri->renamed_commands);
|
||||
|
||||
/* Clear state into the master if needed. */
|
||||
if ((ri->flags & SRI_SLAVE) && (ri->flags & SRI_PROMOTED) && ri->master)
|
||||
@ -1572,6 +1598,21 @@ char *sentinelGetInstanceTypeString(sentinelRedisInstance *ri) {
|
||||
else return "unknown";
|
||||
}
|
||||
|
||||
/* This function is used in order to send commands to Redis instances: the
|
||||
* commands we send from Sentinel may be renamed, a common case is a master
|
||||
* with CONFIG and SLAVEOF commands renamed for security concerns. In that
|
||||
* case we check the ri->renamed_command table (or if the instance is a slave,
|
||||
* we check the one of the master), and map the command that we should send
|
||||
* to the set of renamed commads. However, if the command was not renamed,
|
||||
* we just return "command" itself. */
|
||||
char *sentinelInstanceMapCommand(sentinelRedisInstance *ri, char *command) {
|
||||
sds sc = sdsnew(command);
|
||||
if (ri->master) ri = ri->master;
|
||||
char *retval = dictFetchValue(ri->renamed_commands, sc);
|
||||
sdsfree(sc);
|
||||
return retval ? retval : command;
|
||||
}
|
||||
|
||||
/* ============================ Config handling ============================= */
|
||||
char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
sentinelRedisInstance *ri;
|
||||
@ -1654,16 +1695,18 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->leader_epoch = strtoull(argv[2],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"known-slave") && argc == 4) {
|
||||
} else if ((!strcasecmp(argv[0],"known-slave") ||
|
||||
!strcasecmp(argv[0],"known-replica")) && argc == 4)
|
||||
{
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
/* known-slave <name> <ip> <port> */
|
||||
/* known-replica <name> <ip> <port> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,argv[2],
|
||||
atoi(argv[3]), ri->quorum, ri)) == NULL)
|
||||
{
|
||||
return "Wrong hostname or port for slave.";
|
||||
return "Wrong hostname or port for replica.";
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"known-sentinel") &&
|
||||
(argc == 4 || argc == 5)) {
|
||||
@ -1681,6 +1724,17 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
si->runid = sdsnew(argv[4]);
|
||||
sentinelTryConnectionSharing(si);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rename-command") && argc == 4) {
|
||||
/* rename-command <name> <command> <renamed-command> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
sds oldcmd = sdsnew(argv[2]);
|
||||
sds newcmd = sdsnew(argv[3]);
|
||||
if (dictAdd(ri->renamed_commands,oldcmd,newcmd) != DICT_OK) {
|
||||
sdsfree(oldcmd);
|
||||
sdsfree(newcmd);
|
||||
return "Same command renamed multiple times with rename-command.";
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"announce-ip") && argc == 2) {
|
||||
/* announce-ip <ip-address> */
|
||||
if (strlen(argv[1]))
|
||||
@ -1810,7 +1864,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
if (sentinelAddrIsEqual(slave_addr,master_addr))
|
||||
slave_addr = master->addr;
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel known-slave %s %s %d",
|
||||
"sentinel known-replica %s %s %d",
|
||||
master->name, slave_addr->ip, slave_addr->port);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
@ -1827,6 +1881,18 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
dictReleaseIterator(di2);
|
||||
|
||||
/* sentinel rename-command */
|
||||
di2 = dictGetIterator(master->renamed_commands);
|
||||
while((de = dictNext(di2)) != NULL) {
|
||||
sds oldname = dictGetKey(de);
|
||||
sds newname = dictGetVal(de);
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel rename-command %s %s %s",
|
||||
master->name, oldname, newname);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
dictReleaseIterator(di2);
|
||||
}
|
||||
|
||||
/* sentinel current-epoch is a global state valid for all the masters. */
|
||||
@ -1883,15 +1949,29 @@ werr:
|
||||
/* Send the AUTH command with the specified master password if needed.
|
||||
* Note that for slaves the password set for the master is used.
|
||||
*
|
||||
* In case this Sentinel requires a password as well, via the "requirepass"
|
||||
* configuration directive, we assume we should use the local password in
|
||||
* order to authenticate when connecting with the other Sentinels as well.
|
||||
* So basically all the Sentinels share the same password and use it to
|
||||
* authenticate reciprocally.
|
||||
*
|
||||
* We don't check at all if the command was successfully transmitted
|
||||
* to the instance as if it fails Sentinel will detect the instance down,
|
||||
* will disconnect and reconnect the link and so forth. */
|
||||
void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
|
||||
char *auth_pass = (ri->flags & SRI_MASTER) ? ri->auth_pass :
|
||||
ri->master->auth_pass;
|
||||
char *auth_pass = NULL;
|
||||
|
||||
if (ri->flags & SRI_MASTER) {
|
||||
auth_pass = ri->auth_pass;
|
||||
} else if (ri->flags & SRI_SLAVE) {
|
||||
auth_pass = ri->master->auth_pass;
|
||||
} else if (ri->flags & SRI_SENTINEL) {
|
||||
auth_pass = ACLDefaultUserFirstPassword();
|
||||
}
|
||||
|
||||
if (auth_pass) {
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "AUTH %s",
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
|
||||
sentinelInstanceMapCommand(ri,"AUTH"),
|
||||
auth_pass) == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
}
|
||||
@ -1907,7 +1987,9 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
|
||||
|
||||
snprintf(name,sizeof(name),"sentinel-%.8s-%s",sentinel.myid,type);
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri,
|
||||
"CLIENT SETNAME %s", name) == C_OK)
|
||||
"%s SETNAME %s",
|
||||
sentinelInstanceMapCommand(ri,"CLIENT"),
|
||||
name) == C_OK)
|
||||
{
|
||||
ri->link->pending_commands++;
|
||||
}
|
||||
@ -1969,8 +2051,9 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
sentinelSetClientName(ri,link->pc,"pubsub");
|
||||
/* Now we subscribe to the Sentinels "Hello" channel. */
|
||||
retval = redisAsyncCommand(link->pc,
|
||||
sentinelReceiveHelloMessages, ri, "SUBSCRIBE %s",
|
||||
SENTINEL_HELLO_CHANNEL);
|
||||
sentinelReceiveHelloMessages, ri, "%s %s",
|
||||
sentinelInstanceMapCommand(ri,"SUBSCRIBE"),
|
||||
SENTINEL_HELLO_CHANNEL);
|
||||
if (retval != C_OK) {
|
||||
/* If we can't subscribe, the Pub/Sub connection is useless
|
||||
* and we can simply disconnect it and try again. */
|
||||
@ -2304,8 +2387,11 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
|
||||
{
|
||||
if (redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri,
|
||||
"SCRIPT KILL") == C_OK)
|
||||
"%s KILL",
|
||||
sentinelInstanceMapCommand(ri,"SCRIPT")) == C_OK)
|
||||
{
|
||||
ri->link->pending_commands++;
|
||||
}
|
||||
ri->flags |= SRI_SCRIPT_KILL_SENT;
|
||||
}
|
||||
}
|
||||
@ -2468,7 +2554,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
|
||||
}
|
||||
|
||||
/* Send an "Hello" message via Pub/Sub to the specified 'ri' Redis
|
||||
* instance in order to broadcast the current configuraiton for this
|
||||
* instance in order to broadcast the current configuration for this
|
||||
* master, and to advertise the existence of this Sentinel at the same time.
|
||||
*
|
||||
* The message has the following format:
|
||||
@ -2511,8 +2597,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
|
||||
master->name,master_addr->ip,master_addr->port,
|
||||
(unsigned long long) master->config_epoch);
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelPublishReplyCallback, ri, "PUBLISH %s %s",
|
||||
SENTINEL_HELLO_CHANNEL,payload);
|
||||
sentinelPublishReplyCallback, ri, "%s %s %s",
|
||||
sentinelInstanceMapCommand(ri,"PUBLISH"),
|
||||
SENTINEL_HELLO_CHANNEL,payload);
|
||||
if (retval != C_OK) return C_ERR;
|
||||
ri->link->pending_commands++;
|
||||
return C_OK;
|
||||
@ -2557,13 +2644,14 @@ int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master) {
|
||||
* queued in the connection. */
|
||||
int sentinelSendPing(sentinelRedisInstance *ri) {
|
||||
int retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelPingReplyCallback, ri, "PING");
|
||||
sentinelPingReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"PING"));
|
||||
if (retval == C_OK) {
|
||||
ri->link->pending_commands++;
|
||||
ri->link->last_ping_time = mstime();
|
||||
/* We update the active ping time only if we received the pong for
|
||||
* the previous ping, otherwise we are technically waiting since the
|
||||
* first ping that did not received a reply. */
|
||||
* first ping that did not receive a reply. */
|
||||
if (ri->link->act_ping_time == 0)
|
||||
ri->link->act_ping_time = ri->link->last_ping_time;
|
||||
return 1;
|
||||
@ -2621,7 +2709,8 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
(now - ri->info_refresh) > info_period))
|
||||
{
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelInfoReplyCallback, ri, "INFO");
|
||||
sentinelInfoReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"INFO"));
|
||||
if (retval == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
|
||||
@ -2658,7 +2747,7 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
|
||||
void *mbl;
|
||||
int fields = 0;
|
||||
|
||||
mbl = addDeferredMultiBulkLength(c);
|
||||
mbl = addReplyDeferredLen(c);
|
||||
|
||||
addReplyBulkCString(c,"name");
|
||||
addReplyBulkCString(c,ri->name);
|
||||
@ -2839,7 +2928,7 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
|
||||
fields++;
|
||||
}
|
||||
|
||||
setDeferredMultiBulkLength(c,mbl,fields*2);
|
||||
setDeferredMapLen(c,mbl,fields);
|
||||
}
|
||||
|
||||
/* Output a number of instances contained inside a dictionary as
|
||||
@ -2849,7 +2938,7 @@ void addReplyDictOfRedisInstances(client *c, dict *instances) {
|
||||
dictEntry *de;
|
||||
|
||||
di = dictGetIterator(instances);
|
||||
addReplyMultiBulkLen(c,dictSize(instances));
|
||||
addReplyArrayLen(c,dictSize(instances));
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
|
||||
@ -2912,8 +3001,10 @@ void sentinelCommand(client *c) {
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
addReplySentinelRedisInstance(c,ri);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"slaves")) {
|
||||
/* SENTINEL SLAVES <master-name> */
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"slaves") ||
|
||||
!strcasecmp(c->argv[1]->ptr,"replicas"))
|
||||
{
|
||||
/* SENTINEL REPLICAS <master-name> */
|
||||
sentinelRedisInstance *ri;
|
||||
|
||||
if (c->argc != 3) goto numargserr;
|
||||
@ -2977,7 +3068,7 @@ void sentinelCommand(client *c) {
|
||||
|
||||
/* Reply with a three-elements multi-bulk reply:
|
||||
* down state, leader, vote epoch. */
|
||||
addReplyMultiBulkLen(c,3);
|
||||
addReplyArrayLen(c,3);
|
||||
addReply(c, isdown ? shared.cone : shared.czero);
|
||||
addReplyBulkCString(c, leader ? leader : "*");
|
||||
addReplyLongLong(c, (long long)leader_epoch);
|
||||
@ -2993,11 +3084,11 @@ void sentinelCommand(client *c) {
|
||||
if (c->argc != 3) goto numargserr;
|
||||
ri = sentinelGetMasterByName(c->argv[2]->ptr);
|
||||
if (ri == NULL) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
addReplyNullArray(c);
|
||||
} else {
|
||||
sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri);
|
||||
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,addr->ip);
|
||||
addReplyBulkLongLong(c,addr->port);
|
||||
}
|
||||
@ -3013,7 +3104,7 @@ void sentinelCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
if (sentinelSelectSlave(ri) == NULL) {
|
||||
addReplySds(c,sdsnew("-NOGOODSLAVE No suitable slave to promote\r\n"));
|
||||
addReplySds(c,sdsnew("-NOGOODSLAVE No suitable replica to promote\r\n"));
|
||||
return;
|
||||
}
|
||||
serverLog(LL_WARNING,"Executing user requested FAILOVER of '%s'",
|
||||
@ -3115,7 +3206,7 @@ void sentinelCommand(client *c) {
|
||||
addReplySds(c,e);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set")) {
|
||||
if (c->argc < 3 || c->argc % 2 == 0) goto numargserr;
|
||||
if (c->argc < 3) goto numargserr;
|
||||
sentinelSetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) {
|
||||
/* SENTINEL INFO-CACHE <name> */
|
||||
@ -3147,7 +3238,7 @@ void sentinelCommand(client *c) {
|
||||
* 3.) other master name
|
||||
* ...
|
||||
*/
|
||||
addReplyMultiBulkLen(c,dictSize(masters_local) * 2);
|
||||
addReplyArrayLen(c,dictSize(masters_local) * 2);
|
||||
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
@ -3155,25 +3246,25 @@ void sentinelCommand(client *c) {
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
addReplyBulkCBuffer(c,ri->name,strlen(ri->name));
|
||||
addReplyMultiBulkLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c, now - ri->info_refresh);
|
||||
if (ri->info)
|
||||
addReplyBulkCBuffer(c,ri->info,sdslen(ri->info));
|
||||
else
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
|
||||
dictIterator *sdi;
|
||||
dictEntry *sde;
|
||||
sdi = dictGetIterator(ri->slaves);
|
||||
while ((sde = dictNext(sdi)) != NULL) {
|
||||
sentinelRedisInstance *sri = dictGetVal(sde);
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c, now - sri->info_refresh);
|
||||
if (sri->info)
|
||||
addReplyBulkCBuffer(c,sri->info,sdslen(sri->info));
|
||||
else
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyNull(c);
|
||||
}
|
||||
dictReleaseIterator(sdi);
|
||||
}
|
||||
@ -3195,9 +3286,9 @@ void sentinelCommand(client *c) {
|
||||
sentinel.simfailure_flags |=
|
||||
SENTINEL_SIMFAILURE_CRASH_AFTER_PROMOTION;
|
||||
serverLog(LL_WARNING,"Failure simulation: this Sentinel "
|
||||
"will crash after promoting the selected slave to master");
|
||||
"will crash after promoting the selected replica to master");
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"help")) {
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"crash-after-election");
|
||||
addReplyBulkCString(c,"crash-after-promotion");
|
||||
} else {
|
||||
@ -3291,15 +3382,15 @@ void sentinelInfoCommand(client *c) {
|
||||
addReplyBulkSds(c, info);
|
||||
}
|
||||
|
||||
/* Implements Sentinel verison of the ROLE command. The output is
|
||||
/* Implements Sentinel version of the ROLE command. The output is
|
||||
* "sentinel" and the list of currently monitored master names. */
|
||||
void sentinelRoleCommand(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCBuffer(c,"sentinel",8);
|
||||
addReplyMultiBulkLen(c,dictSize(sentinel.masters));
|
||||
addReplyArrayLen(c,dictSize(sentinel.masters));
|
||||
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
@ -3314,39 +3405,50 @@ void sentinelRoleCommand(client *c) {
|
||||
void sentinelSetCommand(client *c) {
|
||||
sentinelRedisInstance *ri;
|
||||
int j, changes = 0;
|
||||
char *option, *value;
|
||||
int badarg = 0; /* Bad argument position for error reporting. */
|
||||
char *option;
|
||||
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
|
||||
/* Process option - value pairs. */
|
||||
for (j = 3; j < c->argc; j += 2) {
|
||||
for (j = 3; j < c->argc; j++) {
|
||||
int moreargs = (c->argc-1) - j;
|
||||
option = c->argv[j]->ptr;
|
||||
value = c->argv[j+1]->ptr;
|
||||
robj *o = c->argv[j+1];
|
||||
long long ll;
|
||||
int old_j = j; /* Used to know what to log as an event. */
|
||||
|
||||
if (!strcasecmp(option,"down-after-milliseconds")) {
|
||||
if (!strcasecmp(option,"down-after-milliseconds") && moreargs > 0) {
|
||||
/* down-after-millisecodns <milliseconds> */
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
goto badfmt;
|
||||
}
|
||||
ri->down_after_period = ll;
|
||||
sentinelPropagateDownAfterPeriod(ri);
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"failover-timeout")) {
|
||||
} else if (!strcasecmp(option,"failover-timeout") && moreargs > 0) {
|
||||
/* failover-timeout <milliseconds> */
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
goto badfmt;
|
||||
}
|
||||
ri->failover_timeout = ll;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"parallel-syncs")) {
|
||||
} else if (!strcasecmp(option,"parallel-syncs") && moreargs > 0) {
|
||||
/* parallel-syncs <milliseconds> */
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
goto badfmt;
|
||||
}
|
||||
ri->parallel_syncs = ll;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"notification-script")) {
|
||||
} else if (!strcasecmp(option,"notification-script") && moreargs > 0) {
|
||||
/* notification-script <path> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
if (sentinel.deny_scripts_reconfig) {
|
||||
addReplyError(c,
|
||||
"Reconfiguration of scripts path is denied for "
|
||||
@ -3364,8 +3466,9 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->notification_script);
|
||||
ri->notification_script = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"client-reconfig-script")) {
|
||||
} else if (!strcasecmp(option,"client-reconfig-script") && moreargs > 0) {
|
||||
/* client-reconfig-script <path> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
if (sentinel.deny_scripts_reconfig) {
|
||||
addReplyError(c,
|
||||
"Reconfiguration of scripts path is denied for "
|
||||
@ -3384,24 +3487,65 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->client_reconfig_script);
|
||||
ri->client_reconfig_script = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"auth-pass")) {
|
||||
} else if (!strcasecmp(option,"auth-pass") && moreargs > 0) {
|
||||
/* auth-pass <password> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
sdsfree(ri->auth_pass);
|
||||
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"quorum")) {
|
||||
} else if (!strcasecmp(option,"quorum") && moreargs > 0) {
|
||||
/* quorum <count> */
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
|
||||
robj *o = c->argv[++j];
|
||||
if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
|
||||
badarg = j;
|
||||
goto badfmt;
|
||||
}
|
||||
ri->quorum = ll;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"rename-command") && moreargs > 1) {
|
||||
/* rename-command <oldname> <newname> */
|
||||
sds oldname = c->argv[++j]->ptr;
|
||||
sds newname = c->argv[++j]->ptr;
|
||||
|
||||
if ((sdslen(oldname) == 0) || (sdslen(newname) == 0)) {
|
||||
badarg = sdslen(newname) ? j-1 : j;
|
||||
goto badfmt;
|
||||
}
|
||||
|
||||
/* Remove any older renaming for this command. */
|
||||
dictDelete(ri->renamed_commands,oldname);
|
||||
|
||||
/* If the target name is the same as the source name there
|
||||
* is no need to add an entry mapping to itself. */
|
||||
if (!dictSdsKeyCaseCompare(NULL,oldname,newname)) {
|
||||
oldname = sdsdup(oldname);
|
||||
newname = sdsdup(newname);
|
||||
dictAdd(ri->renamed_commands,oldname,newname);
|
||||
}
|
||||
changes++;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unknown option '%s' for SENTINEL SET",
|
||||
option);
|
||||
addReplyErrorFormat(c,"Unknown option or number of arguments for "
|
||||
"SENTINEL SET '%s'", option);
|
||||
if (changes) sentinelFlushConfig();
|
||||
return;
|
||||
}
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",option,value);
|
||||
|
||||
/* Log the event. */
|
||||
int numargs = j-old_j+1;
|
||||
switch(numargs) {
|
||||
case 2:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",c->argv[old_j]->ptr,
|
||||
c->argv[old_j+1]->ptr);
|
||||
break;
|
||||
case 3:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",c->argv[old_j]->ptr,
|
||||
c->argv[old_j+1]->ptr,
|
||||
c->argv[old_j+2]->ptr);
|
||||
break;
|
||||
default:
|
||||
sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",c->argv[old_j]->ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes) sentinelFlushConfig();
|
||||
@ -3411,7 +3555,7 @@ void sentinelSetCommand(client *c) {
|
||||
badfmt: /* Bad format errors */
|
||||
if (changes) sentinelFlushConfig();
|
||||
addReplyErrorFormat(c,"Invalid argument '%s' for SENTINEL SET '%s'",
|
||||
value, option);
|
||||
(char*)c->argv[badarg]->ptr,option);
|
||||
}
|
||||
|
||||
/* Our fake PUBLISH command: it is actually useful only to receive hello messages
|
||||
@ -3449,8 +3593,8 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
|
||||
if (ri->link->cc &&
|
||||
(mstime() - ri->link->cc_conn_time) >
|
||||
SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
|
||||
ri->link->act_ping_time != 0 && /* Ther is a pending ping... */
|
||||
/* The pending ping is delayed, and we did not received
|
||||
ri->link->act_ping_time != 0 && /* There is a pending ping... */
|
||||
/* The pending ping is delayed, and we did not receive
|
||||
* error replies as well. */
|
||||
(mstime() - ri->link->act_ping_time) > (ri->down_after_period/2) &&
|
||||
(mstime() - ri->link->last_pong_time) > (ri->down_after_period/2))
|
||||
@ -3606,7 +3750,7 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
|
||||
*
|
||||
* 1) We believe it is down, or there is a failover in progress.
|
||||
* 2) Sentinel is connected.
|
||||
* 3) We did not received the info within SENTINEL_ASK_PERIOD ms. */
|
||||
* 3) We did not receive the info within SENTINEL_ASK_PERIOD ms. */
|
||||
if ((master->flags & SRI_S_DOWN) == 0) continue;
|
||||
if (ri->link->disconnected) continue;
|
||||
if (!(flags & SENTINEL_ASK_FORCED) &&
|
||||
@ -3617,7 +3761,8 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
|
||||
ll2string(port,sizeof(port),master->addr->port);
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelReceiveIsMasterDownReply, ri,
|
||||
"SENTINEL is-master-down-by-addr %s %s %llu %s",
|
||||
"%s is-master-down-by-addr %s %s %llu %s",
|
||||
sentinelInstanceMapCommand(ri,"SENTINEL"),
|
||||
master->addr->ip, port,
|
||||
sentinel.current_epoch,
|
||||
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
|
||||
@ -3637,7 +3782,7 @@ void sentinelSimFailureCrash(void) {
|
||||
}
|
||||
|
||||
/* Vote for the sentinel with 'req_runid' or return the old vote if already
|
||||
* voted for the specifed 'req_epoch' or one greater.
|
||||
* voted for the specified 'req_epoch' or one greater.
|
||||
*
|
||||
* If a vote is not available returns NULL, otherwise return the Sentinel
|
||||
* runid and populate the leader_epoch with the epoch of the vote. */
|
||||
@ -3788,7 +3933,7 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
|
||||
/* In order to send SLAVEOF in a safe way, we send a transaction performing
|
||||
* the following tasks:
|
||||
* 1) Reconfigure the instance according to the specified host/port params.
|
||||
* 2) Rewrite the configuraiton.
|
||||
* 2) Rewrite the configuration.
|
||||
* 3) Disconnect all clients (but this one sending the commnad) in order
|
||||
* to trigger the ask-master-on-reconnection protocol for connected
|
||||
* clients.
|
||||
@ -3796,17 +3941,21 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
|
||||
* Note that we don't check the replies returned by commands, since we
|
||||
* will observe instead the effects in the next INFO output. */
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "MULTI");
|
||||
sentinelDiscardReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"MULTI"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "SLAVEOF %s %s", host, portstr);
|
||||
sentinelDiscardReplyCallback, ri, "%s %s %s",
|
||||
sentinelInstanceMapCommand(ri,"SLAVEOF"),
|
||||
host, portstr);
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "CONFIG REWRITE");
|
||||
sentinelDiscardReplyCallback, ri, "%s REWRITE",
|
||||
sentinelInstanceMapCommand(ri,"CONFIG"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
@ -3816,12 +3965,14 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
|
||||
* recognized as a syntax error, and the transaction will not fail (but
|
||||
* only the unsupported command will fail). */
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "CLIENT KILL TYPE normal");
|
||||
sentinelDiscardReplyCallback, ri, "%s KILL TYPE normal",
|
||||
sentinelInstanceMapCommand(ri,"CLIENT"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "EXEC");
|
||||
sentinelDiscardReplyCallback, ri, "%s",
|
||||
sentinelInstanceMapCommand(ri,"EXEC"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
|
||||
|
1782
src/server.c
1782
src/server.c
File diff suppressed because it is too large
Load Diff
382
src/server.h
382
src/server.h
@ -78,20 +78,24 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define C_ERR -1
|
||||
|
||||
/* Static server configuration */
|
||||
#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define CONFIG_DEFAULT_DYNAMIC_HZ 1 /* Adapt hz to # of clients.*/
|
||||
#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define CONFIG_MIN_HZ 1
|
||||
#define CONFIG_MAX_HZ 500
|
||||
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port */
|
||||
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog */
|
||||
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* default client timeout: infinite */
|
||||
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
|
||||
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
|
||||
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
|
||||
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
|
||||
#define CONFIG_DEFAULT_DBNUM 16
|
||||
#define CONFIG_DEFAULT_IO_THREADS_NUM 1 /* Single threaded by default */
|
||||
#define CONFIG_DEFAULT_IO_THREADS_DO_READS 0 /* Read + parse from threads? */
|
||||
#define CONFIG_MAX_LINE 1024
|
||||
#define CRON_DBS_PER_CALL 16
|
||||
#define NET_MAX_WRITES_PER_EVENT (1024*64)
|
||||
#define PROTO_SHARED_SELECT_CMDS 10
|
||||
#define OBJ_SHARED_INTEGERS 10000
|
||||
#define OBJ_SHARED_BULKHDR_LEN 32
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages */
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages.*/
|
||||
#define AOF_REWRITE_PERC 100
|
||||
#define AOF_REWRITE_MIN_SIZE (64*1024*1024)
|
||||
#define AOF_REWRITE_ITEMS_PER_CMD 64
|
||||
@ -119,6 +123,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
|
||||
#define CONFIG_DEFAULT_TCP_KEEPALIVE 300
|
||||
#define CONFIG_DEFAULT_PROTECTED_MODE 1
|
||||
#define CONFIG_DEFAULT_GOPHER_ENABLED 0
|
||||
#define CONFIG_DEFAULT_LOGFILE ""
|
||||
#define CONFIG_DEFAULT_SYSLOG_ENABLED 0
|
||||
#define CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
|
||||
@ -127,8 +132,10 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb"
|
||||
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0
|
||||
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
|
||||
#define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0
|
||||
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
||||
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
|
||||
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
|
||||
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP NULL
|
||||
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT 0
|
||||
#define CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
@ -145,6 +152,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC 1
|
||||
#define CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE 0
|
||||
#define CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG 10
|
||||
#define CONFIG_DEFAULT_ACL_FILENAME ""
|
||||
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
|
||||
#define NET_PEER_ID_LEN (NET_IP_STR_LEN+32) /* Must be enough for ip:port */
|
||||
#define CONFIG_BINDADDR_MAX 16
|
||||
@ -163,6 +171,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */
|
||||
#define CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS 1000 /* keys with more than 1000 fields will be processed separately */
|
||||
#define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */
|
||||
#define CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL 10 /* 10% tracking table max fill. */
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
@ -186,6 +195,8 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define LONG_STR_SIZE 21 /* Bytes needed for long -> str + '\0' */
|
||||
#define REDIS_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
|
||||
|
||||
#define LIMIT_PENDING_QUERYBUF (4*1024*1024) /* 4mb */
|
||||
|
||||
/* When configuring the server eventloop, we setup it so that the total number
|
||||
* of file descriptors we can handle are server.maxclients + RESERVED_FDS +
|
||||
* a few more to stay safe. Since RESERVED_FDS defaults to 32, we add 96
|
||||
@ -197,22 +208,48 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
|
||||
/* Command flags. Please check the command table defined in the redis.c file
|
||||
* for more information about the meaning of every flag. */
|
||||
#define CMD_WRITE (1<<0) /* "w" flag */
|
||||
#define CMD_READONLY (1<<1) /* "r" flag */
|
||||
#define CMD_DENYOOM (1<<2) /* "m" flag */
|
||||
#define CMD_MODULE (1<<3) /* Command exported by module. */
|
||||
#define CMD_ADMIN (1<<4) /* "a" flag */
|
||||
#define CMD_PUBSUB (1<<5) /* "p" flag */
|
||||
#define CMD_NOSCRIPT (1<<6) /* "s" flag */
|
||||
#define CMD_RANDOM (1<<7) /* "R" flag */
|
||||
#define CMD_SORT_FOR_SCRIPT (1<<8) /* "S" flag */
|
||||
#define CMD_LOADING (1<<9) /* "l" flag */
|
||||
#define CMD_STALE (1<<10) /* "t" flag */
|
||||
#define CMD_SKIP_MONITOR (1<<11) /* "M" flag */
|
||||
#define CMD_ASKING (1<<12) /* "k" flag */
|
||||
#define CMD_FAST (1<<13) /* "F" flag */
|
||||
#define CMD_MODULE_GETKEYS (1<<14) /* Use the modules getkeys interface. */
|
||||
#define CMD_MODULE_NO_CLUSTER (1<<15) /* Deny on Redis Cluster. */
|
||||
#define CMD_WRITE (1ULL<<0) /* "write" flag */
|
||||
#define CMD_READONLY (1ULL<<1) /* "read-only" flag */
|
||||
#define CMD_DENYOOM (1ULL<<2) /* "use-memory" flag */
|
||||
#define CMD_MODULE (1ULL<<3) /* Command exported by module. */
|
||||
#define CMD_ADMIN (1ULL<<4) /* "admin" flag */
|
||||
#define CMD_PUBSUB (1ULL<<5) /* "pub-sub" flag */
|
||||
#define CMD_NOSCRIPT (1ULL<<6) /* "no-script" flag */
|
||||
#define CMD_RANDOM (1ULL<<7) /* "random" flag */
|
||||
#define CMD_SORT_FOR_SCRIPT (1ULL<<8) /* "to-sort" flag */
|
||||
#define CMD_LOADING (1ULL<<9) /* "ok-loading" flag */
|
||||
#define CMD_STALE (1ULL<<10) /* "ok-stale" flag */
|
||||
#define CMD_SKIP_MONITOR (1ULL<<11) /* "no-monitor" flag */
|
||||
#define CMD_SKIP_SLOWLOG (1ULL<<12) /* "no-slowlog" flag */
|
||||
#define CMD_ASKING (1ULL<<13) /* "cluster-asking" flag */
|
||||
#define CMD_FAST (1ULL<<14) /* "fast" flag */
|
||||
|
||||
/* Command flags used by the module system. */
|
||||
#define CMD_MODULE_GETKEYS (1ULL<<15) /* Use the modules getkeys interface. */
|
||||
#define CMD_MODULE_NO_CLUSTER (1ULL<<16) /* Deny on Redis Cluster. */
|
||||
|
||||
/* Command flags that describe ACLs categories. */
|
||||
#define CMD_CATEGORY_KEYSPACE (1ULL<<17)
|
||||
#define CMD_CATEGORY_READ (1ULL<<18)
|
||||
#define CMD_CATEGORY_WRITE (1ULL<<19)
|
||||
#define CMD_CATEGORY_SET (1ULL<<20)
|
||||
#define CMD_CATEGORY_SORTEDSET (1ULL<<21)
|
||||
#define CMD_CATEGORY_LIST (1ULL<<22)
|
||||
#define CMD_CATEGORY_HASH (1ULL<<23)
|
||||
#define CMD_CATEGORY_STRING (1ULL<<24)
|
||||
#define CMD_CATEGORY_BITMAP (1ULL<<25)
|
||||
#define CMD_CATEGORY_HYPERLOGLOG (1ULL<<26)
|
||||
#define CMD_CATEGORY_GEO (1ULL<<27)
|
||||
#define CMD_CATEGORY_STREAM (1ULL<<28)
|
||||
#define CMD_CATEGORY_PUBSUB (1ULL<<29)
|
||||
#define CMD_CATEGORY_ADMIN (1ULL<<30)
|
||||
#define CMD_CATEGORY_FAST (1ULL<<31)
|
||||
#define CMD_CATEGORY_SLOW (1ULL<<32)
|
||||
#define CMD_CATEGORY_BLOCKING (1ULL<<33)
|
||||
#define CMD_CATEGORY_DANGEROUS (1ULL<<34)
|
||||
#define CMD_CATEGORY_CONNECTION (1ULL<<35)
|
||||
#define CMD_CATEGORY_TRANSACTION (1ULL<<36)
|
||||
#define CMD_CATEGORY_SCRIPTING (1ULL<<37)
|
||||
|
||||
/* AOF states */
|
||||
#define AOF_OFF 0 /* AOF is off */
|
||||
@ -220,8 +257,8 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#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_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 slave 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 */
|
||||
@ -251,6 +288,17 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CLIENT_LUA_DEBUG (1<<25) /* Run EVAL in debug mode. */
|
||||
#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_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. */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@ -349,6 +397,12 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define AOF_FSYNC_EVERYSEC 2
|
||||
#define CONFIG_DEFAULT_AOF_FSYNC AOF_FSYNC_EVERYSEC
|
||||
|
||||
/* Replication diskless load defines */
|
||||
#define REPL_DISKLESS_LOAD_DISABLED 0
|
||||
#define REPL_DISKLESS_LOAD_WHEN_DB_EMPTY 1
|
||||
#define REPL_DISKLESS_LOAD_SWAPDB 2
|
||||
#define CONFIG_DEFAULT_REPL_DISKLESS_LOAD REPL_DISKLESS_LOAD_DISABLED
|
||||
|
||||
/* Zipped structures related defaults */
|
||||
#define OBJ_HASH_MAX_ZIPLIST_ENTRIES 512
|
||||
#define OBJ_HASH_MAX_ZIPLIST_VALUE 64
|
||||
@ -435,7 +489,8 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define NOTIFY_STREAM (1<<10) /* t */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */
|
||||
#define NOTIFY_KEY_MISS (1<<11) /* m */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_KEY_MISS) /* A flag */
|
||||
|
||||
/* Get the first bind addr or NULL */
|
||||
#define NET_FIRST_BIND_ADDR (server.bindaddr_count ? server.bindaddr[0] : NULL)
|
||||
@ -483,6 +538,10 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK)
|
||||
#define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS)
|
||||
|
||||
/* Bit flags for moduleTypeAuxSaveFunc */
|
||||
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||
|
||||
struct RedisModule;
|
||||
struct RedisModuleIO;
|
||||
struct RedisModuleDigest;
|
||||
@ -495,6 +554,8 @@ struct redisObject;
|
||||
* is deleted. */
|
||||
typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
|
||||
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
|
||||
typedef int (*moduleTypeAuxLoadFunc)(struct RedisModuleIO *rdb, int encver, int when);
|
||||
typedef void (*moduleTypeAuxSaveFunc)(struct RedisModuleIO *rdb, int when);
|
||||
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
|
||||
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
|
||||
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
||||
@ -511,6 +572,9 @@ typedef struct RedisModuleType {
|
||||
moduleTypeMemUsageFunc mem_usage;
|
||||
moduleTypeDigestFunc digest;
|
||||
moduleTypeFreeFunc free;
|
||||
moduleTypeAuxLoadFunc aux_load;
|
||||
moduleTypeAuxSaveFunc aux_save;
|
||||
int aux_save_triggers;
|
||||
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
|
||||
} moduleType;
|
||||
|
||||
@ -545,16 +609,18 @@ typedef struct RedisModuleIO {
|
||||
int ver; /* Module serialization version: 1 (old),
|
||||
* 2 (current version with opcodes annotation). */
|
||||
struct RedisModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/
|
||||
struct redisObject *key; /* Optional name of key processed */
|
||||
} RedisModuleIO;
|
||||
|
||||
/* Macro to initialize an IO context. Note that the 'ver' field is populated
|
||||
* inside rdb.c according to the version of the value to load. */
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr) do { \
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr,keyptr) do { \
|
||||
iovar.rio = rioptr; \
|
||||
iovar.type = mtype; \
|
||||
iovar.bytes = 0; \
|
||||
iovar.error = 0; \
|
||||
iovar.ver = 0; \
|
||||
iovar.key = keyptr; \
|
||||
iovar.ctx = NULL; \
|
||||
} while(0);
|
||||
|
||||
@ -604,6 +670,11 @@ typedef struct redisObject {
|
||||
void *ptr;
|
||||
} robj;
|
||||
|
||||
/* 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. */
|
||||
char *getObjectTypeName(robj*);
|
||||
|
||||
/* 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
|
||||
@ -617,6 +688,13 @@ typedef struct redisObject {
|
||||
|
||||
struct evictionPoolEntry; /* Defined in evict.c */
|
||||
|
||||
/* This structure is used in order to represent the output buffer of a client,
|
||||
* which is actually a linked list of blocks like that, that is: client->reply. */
|
||||
typedef struct clientReplyBlock {
|
||||
size_t size, used;
|
||||
char buf[];
|
||||
} clientReplyBlock;
|
||||
|
||||
/* 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. */
|
||||
@ -641,6 +719,9 @@ typedef struct multiCmd {
|
||||
typedef struct multiState {
|
||||
multiCmd *commands; /* Array of MULTI commands */
|
||||
int count; /* Total number of MULTI commands */
|
||||
int cmd_flags; /* The accumulated command flags OR-ed together.
|
||||
So if at least a command has a given flag, it
|
||||
will be set in this field. */
|
||||
int minreplicas; /* MINREPLICAS for synchronous replication */
|
||||
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
|
||||
} multiState;
|
||||
@ -663,6 +744,7 @@ typedef struct blockingState {
|
||||
robj *xread_group; /* XREADGROUP group name. */
|
||||
robj *xread_consumer; /* XREADGROUP consumer name. */
|
||||
mstime_t xread_retry_time, xread_retry_ttl;
|
||||
int xread_group_noack;
|
||||
|
||||
/* BLOCKED_WAIT */
|
||||
int numreplicas; /* Number of replicas we are waiting for ACK. */
|
||||
@ -690,21 +772,69 @@ typedef struct readyList {
|
||||
robj *key;
|
||||
} readyList;
|
||||
|
||||
/* This structure represents a Redis user. This is useful for ACLs, the
|
||||
* user is associated to the connection after the connection is authenticated.
|
||||
* If there is no associated user, the connection uses the default user. */
|
||||
#define USER_COMMAND_BITS_COUNT 1024 /* The total number of command bits
|
||||
in the user structure. The last valid
|
||||
command ID we can set in the user
|
||||
is USER_COMMAND_BITS_COUNT-1. */
|
||||
#define USER_FLAG_ENABLED (1<<0) /* The user is active. */
|
||||
#define USER_FLAG_DISABLED (1<<1) /* The user is disabled. */
|
||||
#define USER_FLAG_ALLKEYS (1<<2) /* The user can mention any key. */
|
||||
#define USER_FLAG_ALLCOMMANDS (1<<3) /* The user can run all commands. */
|
||||
#define USER_FLAG_NOPASS (1<<4) /* The user requires no password, any
|
||||
provided password will work. For the
|
||||
default user, this also means that
|
||||
no AUTH is needed, and every
|
||||
connection is immediately
|
||||
authenticated. */
|
||||
typedef struct user {
|
||||
sds name; /* The username as an SDS string. */
|
||||
uint64_t flags; /* See USER_FLAG_* */
|
||||
|
||||
/* The bit in allowed_commands is set if this user has the right to
|
||||
* execute this command. In commands having subcommands, if this bit is
|
||||
* set, then all the subcommands are also available.
|
||||
*
|
||||
* If the bit for a given command is NOT set and the command has
|
||||
* subcommands, Redis will also check allowed_subcommands in order to
|
||||
* understand if the command can be executed. */
|
||||
uint64_t allowed_commands[USER_COMMAND_BITS_COUNT/64];
|
||||
|
||||
/* This array points, for each command ID (corresponding to the command
|
||||
* bit set in allowed_commands), to an array of SDS strings, terminated by
|
||||
* a NULL pointer, with all the sub commands that can be executed for
|
||||
* this command. When no subcommands matching is used, the field is just
|
||||
* set to NULL to avoid allocating USER_COMMAND_BITS_COUNT pointers. */
|
||||
sds **allowed_subcommands;
|
||||
list *passwords; /* A list of SDS valid passwords for this user. */
|
||||
list *patterns; /* A list of allowed key patterns. If this field is NULL
|
||||
the user cannot mention any key in a command, unless
|
||||
the flag ALLKEYS is set in the user. */
|
||||
} user;
|
||||
|
||||
/* With multiplexing we need to take per-client state.
|
||||
* Clients are taken in a linked list. */
|
||||
typedef struct client {
|
||||
uint64_t id; /* Client incremental unique ID. */
|
||||
int fd; /* Client socket. */
|
||||
int resp; /* RESP protocol version. Can be 2 or 3. */
|
||||
redisDb *db; /* Pointer to currently SELECTed DB. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
sds querybuf; /* Buffer we use to accumulate client queries. */
|
||||
sds pending_querybuf; /* If this is a master, this buffer represents the
|
||||
yet not applied replication stream that we
|
||||
are receiving from the master. */
|
||||
size_t qb_pos; /* The position we have read in querybuf. */
|
||||
sds pending_querybuf; /* If this client is flagged as master, this buffer
|
||||
represents the yet not applied portion of the
|
||||
replication stream that we are receiving from
|
||||
the master. */
|
||||
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
|
||||
int argc; /* Num of arguments of current command. */
|
||||
robj **argv; /* Arguments of current command. */
|
||||
struct redisCommand *cmd, *lastcmd; /* Last command executed. */
|
||||
user *user; /* User associated with this connection. If the
|
||||
user is set to NULL the connection can do
|
||||
anything (admin). */
|
||||
int reqtype; /* Request protocol type: PROTO_REQ_* */
|
||||
int multibulklen; /* Number of multi bulk arguments left to read. */
|
||||
long bulklen; /* Length of bulk argument in multi bulk request. */
|
||||
@ -715,10 +845,10 @@ 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;
|
||||
int flags; /* Client flags: CLIENT_* macros. */
|
||||
int authenticated; /* When requirepass is non-NULL. */
|
||||
uint64_t flags; /* Client flags: CLIENT_* macros. */
|
||||
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 repl_put_online_on_ack; /* Install slave write handler on first ACK. */
|
||||
int repldbfd; /* Replication DB file descriptor. */
|
||||
off_t repldboff; /* Replication DB file offset. */
|
||||
off_t repldbsize; /* Replication DB file size. */
|
||||
@ -744,6 +874,11 @@ typedef struct client {
|
||||
sds peerid; /* Cached peer ID. */
|
||||
listNode *client_list_node; /* list node in client list */
|
||||
|
||||
/* 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];
|
||||
@ -761,14 +896,14 @@ struct moduleLoadQueueEntry {
|
||||
};
|
||||
|
||||
struct sharedObjectsStruct {
|
||||
robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space,
|
||||
*colon, *nullbulk, *nullmultibulk, *queued,
|
||||
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
|
||||
*colon, *queued, *null[4], *nullarray[4],
|
||||
*emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
|
||||
*rpop, *lpop, *lpush, *zpopmin, *zpopmax, *emptyscan,
|
||||
*rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
|
||||
*select[PROTO_SHARED_SELECT_CMDS],
|
||||
*integers[OBJ_SHARED_INTEGERS],
|
||||
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@ -840,6 +975,7 @@ struct redisMemOverhead {
|
||||
size_t clients_slaves;
|
||||
size_t clients_normal;
|
||||
size_t aof_buffer;
|
||||
size_t lua_caches;
|
||||
size_t overhead_total;
|
||||
size_t dataset;
|
||||
size_t total_keys;
|
||||
@ -847,11 +983,11 @@ struct redisMemOverhead {
|
||||
float dataset_perc;
|
||||
float peak_perc;
|
||||
float total_frag;
|
||||
size_t total_frag_bytes;
|
||||
ssize_t total_frag_bytes;
|
||||
float allocator_frag;
|
||||
size_t allocator_frag_bytes;
|
||||
ssize_t allocator_frag_bytes;
|
||||
float allocator_rss;
|
||||
size_t allocator_rss_bytes;
|
||||
ssize_t allocator_rss_bytes;
|
||||
float rss_extra;
|
||||
size_t rss_extra_bytes;
|
||||
size_t num_dbs;
|
||||
@ -912,16 +1048,19 @@ struct redisServer {
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
char *executable; /* Absolute executable file path. */
|
||||
char **exec_argv; /* Executable argv vector (copy). */
|
||||
int dynamic_hz; /* Change hz value depending on # of clients. */
|
||||
int config_hz; /* Configured HZ value. May be different than
|
||||
the actual 'hz' field value if dynamic-hz
|
||||
is enabled. */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table */
|
||||
dict *orig_commands; /* Command table before command renaming. */
|
||||
aeEventLoop *el;
|
||||
unsigned int lruclock; /* Clock for LRU eviction */
|
||||
_Atomic unsigned int lruclock; /* Clock for LRU eviction */
|
||||
int shutdown_asap; /* SHUTDOWN needed ASAP */
|
||||
int activerehashing; /* Incremental rehash in serverCron() */
|
||||
int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */
|
||||
char *requirepass; /* Pass for AUTH command, or NULL */
|
||||
char *pidfile; /* PID file path */
|
||||
int arch_bits; /* 32 or 64 depending on sizeof(long) */
|
||||
int cronloops; /* Number of times the cron function run */
|
||||
@ -930,7 +1069,9 @@ struct redisServer {
|
||||
size_t initial_memory_usage; /* Bytes used after initialization. */
|
||||
int always_show_logo; /* Show logo even for non-stdout logging. */
|
||||
/* Modules */
|
||||
dict *moduleapi; /* Exported APIs dictionary for modules. */
|
||||
dict *moduleapi; /* Exported core APIs dictionary for modules. */
|
||||
dict *sharedapi; /* Like moduleapi but containing the APIs that
|
||||
modules share with each other. */
|
||||
list *loadmodule_queue; /* List of modules to load at startup. */
|
||||
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
||||
client blocked on a module command needs
|
||||
@ -950,14 +1091,21 @@ struct redisServer {
|
||||
list *clients; /* List of active clients */
|
||||
list *clients_to_close; /* Clients to close asynchronously */
|
||||
list *clients_pending_write; /* There is to write or install handler. */
|
||||
list *clients_pending_read; /* Client has pending read socket buffers. */
|
||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||
client *current_client; /* Current client, only used on crash report */
|
||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||
int clients_paused; /* True if clients are currently paused */
|
||||
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
||||
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
|
||||
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
|
||||
uint64_t next_client_id; /* Next client unique ID. Incremental. */
|
||||
_Atomic uint64_t next_client_id; /* Next client unique ID. Incremental. */
|
||||
int protected_mode; /* Don't accept external connections. */
|
||||
int gopher_enabled; /* If true the server will reply to gopher
|
||||
queries. Will still serve RESP2 queries. */
|
||||
int io_threads_num; /* Number of IO threads to use. */
|
||||
int io_threads_do_reads; /* Read and parse from IO threads? */
|
||||
|
||||
/* RDB / AOF loading information */
|
||||
int loading; /* We are loading data from disk if true */
|
||||
off_t loading_total_bytes;
|
||||
@ -968,7 +1116,8 @@ struct redisServer {
|
||||
struct redisCommand *delCommand, *multiCommand, *lpushCommand,
|
||||
*lpopCommand, *rpopCommand, *zpopminCommand,
|
||||
*zpopmaxCommand, *sremCommand, *execCommand,
|
||||
*expireCommand, *pexpireCommand, *xclaimCommand;
|
||||
*expireCommand, *pexpireCommand, *xclaimCommand,
|
||||
*xgroupCommand;
|
||||
/* Fields used only for stats */
|
||||
time_t stat_starttime; /* Server start time */
|
||||
long long stat_numcommands; /* Number of processed commands */
|
||||
@ -996,8 +1145,8 @@ struct redisServer {
|
||||
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
|
||||
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
|
||||
struct malloc_stats cron_malloc_stats; /* sampled in serverCron(). */
|
||||
long long stat_net_input_bytes; /* Bytes read from network. */
|
||||
long long stat_net_output_bytes; /* Bytes written to network. */
|
||||
_Atomic long long stat_net_input_bytes; /* Bytes read from network. */
|
||||
_Atomic long long stat_net_output_bytes; /* Bytes written to network. */
|
||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
||||
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
||||
/* The following two are used to track instantaneous metrics, like
|
||||
@ -1020,13 +1169,14 @@ struct redisServer {
|
||||
int active_defrag_cycle_min; /* minimal effort for defrag in CPU percentage */
|
||||
int active_defrag_cycle_max; /* maximal effort for defrag in CPU percentage */
|
||||
unsigned long active_defrag_max_scan_fields; /* maximum number of fields of set/hash/zset/list to process from within the main dict scan */
|
||||
size_t client_max_querybuf_len; /* Limit for client query buffer length */
|
||||
_Atomic size_t client_max_querybuf_len; /* Limit for client query buffer length */
|
||||
int dbnum; /* Total number of configured DBs */
|
||||
int supervised; /* 1 if supervised, 0 otherwise. */
|
||||
int supervised_mode; /* See SUPERVISED_* */
|
||||
int daemonize; /* True if running as a daemon */
|
||||
clientBufferLimitsConfig client_obuf_limits[CLIENT_TYPE_OBUF_COUNT];
|
||||
/* AOF persistence */
|
||||
int aof_enabled; /* AOF configuration */
|
||||
int aof_state; /* AOF_(ON|OFF|WAIT_REWRITE) */
|
||||
int aof_fsync; /* Kind of fsync() policy */
|
||||
char *aof_filename; /* Name of the AOF file */
|
||||
@ -1035,6 +1185,7 @@ struct redisServer {
|
||||
off_t aof_rewrite_min_size; /* the AOF file is at least N bytes. */
|
||||
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
|
||||
off_t aof_current_size; /* AOF current size. */
|
||||
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
|
||||
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
|
||||
pid_t aof_child_pid; /* PID if rewriting process */
|
||||
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
|
||||
@ -1082,6 +1233,8 @@ struct redisServer {
|
||||
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_key_save_delay; /* Delay in microseconds between keys while
|
||||
* writing the RDB. (for testings) */
|
||||
/* Pipe and data structures for child -> parent info sharing. */
|
||||
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
|
||||
struct {
|
||||
@ -1117,9 +1270,12 @@ struct redisServer {
|
||||
int repl_min_slaves_to_write; /* Min number of slaves to write. */
|
||||
int repl_min_slaves_max_lag; /* Max lag of <count> slaves to write. */
|
||||
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; /* Master send RDB to slaves sockets directly. */
|
||||
int repl_diskless_load; /* Slave parse RDB directly from the socket.
|
||||
* see REPL_DISKLESS_LOAD_* enum */
|
||||
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
|
||||
/* Replication (slave) */
|
||||
char *masteruser; /* AUTH with this user and masterauth with master */
|
||||
char *masterauth; /* AUTH with this password with master */
|
||||
char *masterhost; /* Hostname of master */
|
||||
int masterport; /* Port of master */
|
||||
@ -1137,6 +1293,7 @@ struct redisServer {
|
||||
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
|
||||
int repl_serve_stale_data; /* Serve stale data when link is down? */
|
||||
int repl_slave_ro; /* Slave is read only? */
|
||||
int repl_slave_ignore_maxmemory; /* If true slaves do not evict. */
|
||||
time_t repl_down_since; /* Unix time at which link with master went down */
|
||||
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
|
||||
int slave_priority; /* Reported in INFO and used by Sentinel. */
|
||||
@ -1168,6 +1325,9 @@ struct redisServer {
|
||||
unsigned int blocked_clients_by_type[BLOCKED_NUM];
|
||||
list *unblocked_clients; /* list of clients to unblock before next loop */
|
||||
list *ready_keys; /* List of readyList structures for BLPOP & co */
|
||||
/* Client side caching. */
|
||||
unsigned int tracking_clients; /* # of clients with tracking enabled.*/
|
||||
int tracking_table_max_fill; /* Max fill percentage. */
|
||||
/* 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;
|
||||
@ -1187,8 +1347,10 @@ struct redisServer {
|
||||
int list_max_ziplist_size;
|
||||
int list_compress_depth;
|
||||
/* time cache */
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
_Atomic time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
time_t timezone; /* Cached timezone. As set by tzset(). */
|
||||
int daylight_active; /* Currently in daylight saving time. */
|
||||
long long mstime; /* 'unixtime' with milliseconds resolution. */
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
@ -1208,11 +1370,16 @@ struct redisServer {
|
||||
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
|
||||
int cluster_announce_port; /* base port to announce on cluster bus. */
|
||||
int cluster_announce_bus_port; /* bus port to announce on cluster bus. */
|
||||
int cluster_module_flags; /* Set of flags that Redis modules are able
|
||||
to set in order to suppress certain
|
||||
native Redis Cluster features. Check the
|
||||
REDISMODULE_CLUSTER_FLAG_*. */
|
||||
/* Scripting */
|
||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||
client *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
client *lua_caller; /* The client running EVAL right now, or NULL */
|
||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||
mstime_t lua_time_start; /* Start time of script, milliseconds time */
|
||||
int lua_write_dirty; /* True if a write command was called during the
|
||||
@ -1233,6 +1400,8 @@ struct redisServer {
|
||||
/* Latency monitor */
|
||||
long long latency_monitor_threshold;
|
||||
dict *latency_events;
|
||||
/* ACLs */
|
||||
char *acl_filename; /* ACL Users file. NULL if not configured. */
|
||||
/* Assert & bug reporting */
|
||||
const char *assert_failed;
|
||||
const char *assert_file;
|
||||
@ -1241,12 +1410,6 @@ struct redisServer {
|
||||
int watchdog_period; /* Software watchdog period in ms. 0 = off */
|
||||
/* System hardware info */
|
||||
size_t system_memory_size; /* Total memory in system as reported by OS */
|
||||
|
||||
/* Mutexes used to protect atomic variables when atomic builtins are
|
||||
* not available. */
|
||||
pthread_mutex_t lruclock_mutex;
|
||||
pthread_mutex_t next_client_id_mutex;
|
||||
pthread_mutex_t unixtime_mutex;
|
||||
};
|
||||
|
||||
typedef struct pubsubPattern {
|
||||
@ -1260,8 +1423,8 @@ struct redisCommand {
|
||||
char *name;
|
||||
redisCommandProc *proc;
|
||||
int arity;
|
||||
char *sflags; /* Flags as string representation, one char per flag. */
|
||||
int flags; /* The actual flags, obtained from the 'sflags' field. */
|
||||
char *sflags; /* Flags as string representation, one char per flag. */
|
||||
uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */
|
||||
/* Use a function to determine keys arguments in a command line.
|
||||
* Used for Redis Cluster redirect. */
|
||||
redisGetKeysProc *getkeys_proc;
|
||||
@ -1270,6 +1433,11 @@ struct redisCommand {
|
||||
int lastkey; /* The last argument that's a key */
|
||||
int keystep; /* The step between first and last key */
|
||||
long long microseconds, calls;
|
||||
int id; /* Command ID. This is a progressive ID starting from 0 that
|
||||
is assigned at runtime, and is used in order to check
|
||||
ACLs. A connection is able to execute a given command if
|
||||
the user associated to the connection has this command
|
||||
bit set in the bitmap of allowed commands. */
|
||||
};
|
||||
|
||||
struct redisFunctionSym {
|
||||
@ -1371,7 +1539,8 @@ size_t moduleCount(void);
|
||||
void moduleAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
|
||||
void moduleCallCommandFilters(client *c);
|
||||
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
@ -1390,14 +1559,25 @@ void freeClient(client *c);
|
||||
void freeClientAsync(client *c);
|
||||
void resetClient(client *c);
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void *addDeferredMultiBulkLength(client *c);
|
||||
void setDeferredMultiBulkLength(client *c, void *node, long length);
|
||||
void *addReplyDeferredLen(client *c);
|
||||
void setDeferredArrayLen(client *c, void *node, long length);
|
||||
void setDeferredMapLen(client *c, void *node, long length);
|
||||
void setDeferredSetLen(client *c, void *node, long length);
|
||||
void setDeferredAttributeLen(client *c, void *node, long length);
|
||||
void setDeferredPushLen(client *c, void *node, long length);
|
||||
void processInputBuffer(client *c);
|
||||
void processInputBufferAndReplicate(client *c);
|
||||
void processGopherRequest(client *c);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReplyString(client *c, const char *s, size_t len);
|
||||
void addReplyNull(client *c);
|
||||
void addReplyNullArray(client *c);
|
||||
void addReplyBool(client *c, int b);
|
||||
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
|
||||
void addReplyProto(client *c, const char *s, size_t len);
|
||||
void AddReplyFromClient(client *c, client *src);
|
||||
void addReplyBulk(client *c, robj *obj);
|
||||
void addReplyBulkCString(client *c, const char *s);
|
||||
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
|
||||
@ -1410,17 +1590,24 @@ void addReplyStatus(client *c, const char *status);
|
||||
void addReplyDouble(client *c, double d);
|
||||
void addReplyHumanLongDouble(client *c, long double d);
|
||||
void addReplyLongLong(client *c, long long ll);
|
||||
void addReplyMultiBulkLen(client *c, long length);
|
||||
void addReplyArrayLen(client *c, long length);
|
||||
void addReplyMapLen(client *c, long length);
|
||||
void addReplySetLen(client *c, long length);
|
||||
void addReplyAttributeLen(client *c, long length);
|
||||
void addReplyPushLen(client *c, long length);
|
||||
void addReplyHelp(client *c, const char **help);
|
||||
void addReplySubcommandSyntaxError(client *c);
|
||||
void addReplyLoadedModules(client *c);
|
||||
void copyClientOutputBuffer(client *dst, client *src);
|
||||
size_t sdsZmallocSize(sds s);
|
||||
size_t getStringObjectSdsUsedMemory(robj *o);
|
||||
void freeClientReplyValue(void *o);
|
||||
void *dupClientReplyValue(void *o);
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
char *getClientPeerId(client *client);
|
||||
sds catClientInfoString(sds s, client *client);
|
||||
sds getAllClientsInfoString(void);
|
||||
sds getAllClientsInfoString(int type);
|
||||
void rewriteClientCommandVector(client *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(client *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(client *c, int argc, robj **argv);
|
||||
@ -1437,10 +1624,17 @@ void pauseClients(mstime_t duration);
|
||||
int clientsArePaused(void);
|
||||
int processEventsWhileBlocked(void);
|
||||
int handleClientsWithPendingWrites(void);
|
||||
int handleClientsWithPendingWritesUsingThreads(void);
|
||||
int handleClientsWithPendingReadsUsingThreads(void);
|
||||
int stopThreadedIOIfNeeded(void);
|
||||
int clientHasPendingReplies(client *c);
|
||||
void unlinkClient(client *c);
|
||||
int writeToClient(int fd, client *c, int handler_installed);
|
||||
void linkClient(client *c);
|
||||
void protectClient(client *c);
|
||||
void unprotectClient(client *c);
|
||||
void initThreadedIO(void);
|
||||
client *lookupClientByID(uint64_t id);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(client *c, const char *fmt, ...)
|
||||
@ -1452,6 +1646,15 @@ 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);
|
||||
void trackingInvalidateKeysOnFlush(int dbid);
|
||||
void trackingLimitUsedSlots(void);
|
||||
unsigned long long trackingGetUsedSlots(void);
|
||||
|
||||
/* List data type */
|
||||
void listTypeTryConversion(robj *subject, robj *value);
|
||||
void listTypePush(robj *subject, robj *value, int where);
|
||||
@ -1525,6 +1728,7 @@ int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
void trimStringObjectIfNeeded(robj *o);
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
@ -1563,13 +1767,20 @@ void replicationCacheMasterUsingMyself(void);
|
||||
void feedReplicationBacklog(void *ptr, size_t len);
|
||||
|
||||
/* Generic persistence functions */
|
||||
void startLoading(FILE *fp);
|
||||
void startLoadingFile(FILE* fp, char* filename);
|
||||
void startLoading(size_t size);
|
||||
void loadingProgress(off_t pos);
|
||||
void stopLoading(void);
|
||||
|
||||
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
||||
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
||||
#define DISK_ERROR_TYPE_NONE 0 /* No problems, we can accept writes. */
|
||||
int writeCommandsDeniedByDiskError(void);
|
||||
|
||||
/* RDB persistence */
|
||||
#include "rdb.h"
|
||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
|
||||
void killRDBChild(void);
|
||||
|
||||
/* AOF persistence */
|
||||
void flushAppendOnlyFile(int force);
|
||||
@ -1583,6 +1794,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal);
|
||||
void aofRewriteBufferReset(void);
|
||||
unsigned long aofRewriteBufferSize(void);
|
||||
ssize_t aofReadDiffFromParent(void);
|
||||
void killAppendOnlyChild(void);
|
||||
|
||||
/* Child info */
|
||||
void openChildInfoPipe(void);
|
||||
@ -1590,17 +1802,40 @@ void closeChildInfoPipe(void);
|
||||
void sendChildInfo(int process_type);
|
||||
void receiveChildInfo(void);
|
||||
|
||||
/* acl.c -- Authentication related prototypes. */
|
||||
extern rax *Users;
|
||||
extern user *DefaultUser;
|
||||
void ACLInit(void);
|
||||
/* Return values for ACLCheckUserCredentials(). */
|
||||
#define ACL_OK 0
|
||||
#define ACL_DENIED_CMD 1
|
||||
#define ACL_DENIED_KEY 2
|
||||
int ACLCheckUserCredentials(robj *username, robj *password);
|
||||
int ACLAuthenticateUser(client *c, robj *username, robj *password);
|
||||
unsigned long ACLGetCommandID(const char *cmdname);
|
||||
user *ACLGetUserByName(const char *name, size_t namelen);
|
||||
int ACLCheckCommandPerm(client *c);
|
||||
int ACLSetUser(user *u, const char *op, ssize_t oplen);
|
||||
sds ACLDefaultUserFirstPassword(void);
|
||||
uint64_t ACLGetCommandCategoryFlagByName(const char *name);
|
||||
int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err);
|
||||
char *ACLSetUserStringError(void);
|
||||
int ACLLoadConfiguredUsers(void);
|
||||
sds ACLDescribeUser(user *u);
|
||||
void ACLLoadUsersAtStartup(void);
|
||||
void addReplyCommandCategories(client *c, struct redisCommand *cmd);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
/* Input flags. */
|
||||
#define ZADD_NONE 0
|
||||
#define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */
|
||||
#define ZADD_NX (1<<1) /* Don't touch elements not already existing. */
|
||||
#define ZADD_XX (1<<2) /* Only touch elements already exisitng. */
|
||||
#define ZADD_XX (1<<2) /* Only touch elements already existing. */
|
||||
|
||||
/* Output flags. */
|
||||
#define ZADD_NOP (1<<3) /* Operation not performed because of conditionals.*/
|
||||
#define ZADD_NAN (1<<4) /* Only touch elements already exisitng. */
|
||||
#define ZADD_NAN (1<<4) /* Only touch elements already existing. */
|
||||
#define ZADD_ADDED (1<<5) /* The element was new and was added. */
|
||||
#define ZADD_UPDATED (1<<6) /* The element already existed, score updated. */
|
||||
|
||||
@ -1656,7 +1891,9 @@ int zslLexValueLteMax(sds value, zlexrangespec *spec);
|
||||
|
||||
/* Core functions */
|
||||
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level);
|
||||
size_t freeMemoryGetNotCountedMemory();
|
||||
int freeMemoryIfNeeded(void);
|
||||
int freeMemoryIfNeededAndSafe(void);
|
||||
int processCommand(client *c);
|
||||
void setupSignalHandlers(void);
|
||||
struct redisCommand *lookupCommand(sds name);
|
||||
@ -1720,7 +1957,6 @@ void setTypeConvert(robj *subject, int enc);
|
||||
|
||||
void hashTypeConvert(robj *o, int enc);
|
||||
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
|
||||
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2);
|
||||
int hashTypeExists(robj *o, sds key);
|
||||
int hashTypeDelete(robj *o, sds key);
|
||||
unsigned long hashTypeLength(const robj *o);
|
||||
@ -1744,6 +1980,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify);
|
||||
void freePubsubPattern(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, char *event, robj *key, int dbid);
|
||||
@ -1788,6 +2025,8 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
|
||||
#define EMPTYDB_NO_FLAGS 0 /* No flags. */
|
||||
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*));
|
||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
|
||||
long long dbTotalServerKeyCount();
|
||||
|
||||
int selectDb(client *c, int id);
|
||||
void signalModifiedKey(redisDb *db, robj *key);
|
||||
@ -1805,6 +2044,7 @@ int dbAsyncDelete(redisDb *db, robj *key);
|
||||
void emptyDbAsync(redisDb *db);
|
||||
void slotToKeyFlushAsync(void);
|
||||
size_t lazyfreeGetPendingObjectsCount(void);
|
||||
void freeObjAsync(robj *o);
|
||||
|
||||
/* API to get key arguments from commands */
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
@ -1849,6 +2089,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body);
|
||||
void processUnblockedClients(void);
|
||||
void blockClient(client *c, int btype);
|
||||
void unblockClient(client *c);
|
||||
void queueClientForReprocessing(client *c);
|
||||
void replyToBlockedClientTimedOut(client *c);
|
||||
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit);
|
||||
void disconnectAllBlockedClients(void);
|
||||
@ -1962,7 +2203,7 @@ void ttlCommand(client *c);
|
||||
void touchCommand(client *c);
|
||||
void pttlCommand(client *c);
|
||||
void persistCommand(client *c);
|
||||
void slaveofCommand(client *c);
|
||||
void replicaofCommand(client *c);
|
||||
void roleCommand(client *c);
|
||||
void debugCommand(client *c);
|
||||
void msetCommand(client *c);
|
||||
@ -2034,6 +2275,7 @@ void dumpCommand(client *c);
|
||||
void objectCommand(client *c);
|
||||
void memoryCommand(client *c);
|
||||
void clientCommand(client *c);
|
||||
void helloCommand(client *c);
|
||||
void evalCommand(client *c);
|
||||
void evalShaCommand(client *c);
|
||||
void scriptCommand(client *c);
|
||||
@ -2067,12 +2309,15 @@ void xrevrangeCommand(client *c);
|
||||
void xlenCommand(client *c);
|
||||
void xreadCommand(client *c);
|
||||
void xgroupCommand(client *c);
|
||||
void xsetidCommand(client *c);
|
||||
void xackCommand(client *c);
|
||||
void xpendingCommand(client *c);
|
||||
void xclaimCommand(client *c);
|
||||
void xinfoCommand(client *c);
|
||||
void xdelCommand(client *c);
|
||||
void xtrimCommand(client *c);
|
||||
void lolwutCommand(client *c);
|
||||
void aclCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
@ -2096,6 +2341,7 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len);
|
||||
int memtest_preserving_test(unsigned long *m, size_t bytes, int passes);
|
||||
void mixDigest(unsigned char *digest, void *ptr, size_t len);
|
||||
void xorDigest(unsigned char *digest, void *ptr, size_t len);
|
||||
int populateCommandTableParseFlags(struct redisCommand *c, char *strflags);
|
||||
|
||||
#define redisDebug(fmt, ...) \
|
||||
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
@ -39,7 +39,7 @@
|
||||
#include <errno.h> /* errno program_invocation_name program_invocation_short_name */
|
||||
|
||||
#if !defined(HAVE_SETPROCTITLE)
|
||||
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __DragonFly__)
|
||||
#define HAVE_SETPROCTITLE 1
|
||||
#else
|
||||
#define HAVE_SETPROCTITLE 0
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user