Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fac516950 | ||
|
|
2f65aa2337 | ||
|
|
0a0c2146c8 | ||
|
|
66cef6646c | ||
|
|
4589e861ab | ||
|
|
71d5a10621 | ||
|
|
50a6a3ca38 | ||
|
|
f83a89f82a | ||
|
|
bf905a78f0 | ||
|
|
fad9483fc9 | ||
|
|
6b6e6f6c1e | ||
|
|
b0a0f03b96 | ||
|
|
f1982ca63d | ||
|
|
25e18a7de9 |
No files matched your search
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
** Log Files **
|
||||
These should be KeyDB logs, not syslogs or logs from your container manager. If you are reporting a crash there will be a line in your log stating:
|
||||
"=== KEYDB BUG REPORT START: Cut & paste starting from here ==="
|
||||
|
||||
Please copy everything after this line.
|
||||
|
||||
**To Reproduce**
|
||||
Do you know how to reproduce this? If so please provide repro steps.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,19 +0,0 @@
|
||||
name: C/C++ CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make
|
||||
- name: make test
|
||||
run: make test
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
.*.swp
|
||||
core
|
||||
*.o
|
||||
*.d
|
||||
*.log
|
||||
dump.rdb
|
||||
redis-benchmark
|
||||
@@ -28,7 +27,7 @@ release.h
|
||||
src/transfer.sh
|
||||
src/configs
|
||||
redis.ds
|
||||
src/keydb.conf
|
||||
src/redis.conf
|
||||
src/nodes.conf
|
||||
deps/lua/src/lua
|
||||
deps/lua/src/luac
|
||||
|
||||
Vendored
-58
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"zmalloc.h": "c",
|
||||
"stat.h": "c",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"cctype": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"list": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"fstream": "cpp",
|
||||
"functional": "cpp",
|
||||
"future": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"memory": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"scoped_allocator": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"utility": "cpp",
|
||||
"set": "cpp",
|
||||
"algorithm": "cpp"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ FROM ubuntu:18.04
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -qqy \
|
||||
build-essential nasm autotools-dev autoconf libcurl4-openssl-dev libjemalloc-dev tcl tcl-dev uuid-dev libcurl4-openssl-dev \
|
||||
build-essential nasm autotools-dev autoconf libcurl4-openssl-dev libjemalloc-dev tcl tcl-dev uuid-dev \
|
||||
&& apt-get clean
|
||||
|
||||
CMD make
|
||||
@@ -4,20 +4,16 @@
|
||||
|
||||
##### Need Help? Check out our extensive [documentation](https://docs.keydb.dev).
|
||||
|
||||
##### Have feedback? Take our quick survey: https://www.surveymonkey.com/r/Y9XNS93
|
||||
|
||||
What is KeyDB?
|
||||
--------------
|
||||
|
||||
KeyDB is a high performance fork of Redis with a focus on multithreading, memory efficiency, and high throughput. In addition to multithreading, KeyDB also has features only available in Redis Enterprise such as [Active Replication](https://github.com/JohnSully/KeyDB/wiki/Active-Replication), [FLASH storage](https://github.com/JohnSully/KeyDB/wiki/FLASH-Storage) support, and some not available at all such as direct backup to AWS S3.
|
||||
|
||||
KeyDB maintains full compatibility with the Redis protocol, modules, and scripts. This includes the atomicity guarantees for scripts and transactions. Because KeyDB keeps in sync with Redis development KeyDB is a superset of Redis functionality, making KeyDB a drop in replacement for existing Redis deployments.
|
||||
KeyDB maintains full compatibility with the Redis protocol, modules, and scripts. This includes the atomicity gurantees for scripts and transactions. Because KeyDB keeps in sync with Redis development KeyDB is a superset of Redis functionality, making KeyDB a drop in replacement for existing Redis deployments.
|
||||
|
||||
On the same hardware KeyDB can perform twice as many queries per second as Redis, with 60% lower latency. Active-Replication simplifies hot-spare failover allowing you to easily distribute writes over replicas and use simple TCP based load balancing/failover. KeyDB's higher performance allows you to do more on less hardware which reduces operation costs and complexity.
|
||||
|
||||
<img src="https://keydb.dev/assets/img/blog/5x_opspersecVSdatasize.PNG"/>
|
||||
|
||||
See the full benchmark results and setup information here: https://docs.keydb.dev/blog/2019/10/07/blog-post/
|
||||
<img src=https://cdn-images-1.medium.com/max/1400/1*s7mTb7Qb0kxc951mz8bdgA.png width=420 height=300/><img src=https://cdn-images-1.medium.com/max/1400/1*R00A5U4AFGohGOYHMfT6fA.png height=300/>
|
||||
|
||||
Why fork Redis?
|
||||
---------------
|
||||
|
||||
+120
-127
@@ -1,9 +1,9 @@
|
||||
# KeyDB configuration file example.
|
||||
# Redis configuration file example.
|
||||
#
|
||||
# Note that in order to read the configuration file, KeyDB must be
|
||||
# Note that in order to read the configuration file, Redis must be
|
||||
# started with the file path as first argument:
|
||||
#
|
||||
# ./keydb-server /path/to/keydb.conf
|
||||
# ./keydb-server /path/to/redis.conf
|
||||
|
||||
# Note on units: when memory size is needed, it is possible to specify
|
||||
# it in the usual form of 1k 5GB 4M and so forth:
|
||||
@@ -20,12 +20,12 @@
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
# have a standard template that goes to all KeyDB servers but also need
|
||||
# have a standard template that goes to all Redis servers but also need
|
||||
# to customize a few per-server settings. Include files can include
|
||||
# other files, so use this wisely.
|
||||
#
|
||||
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
|
||||
# from admin or KeyDB Sentinel. Since KeyDB always uses the last processed
|
||||
# from admin or Redis Sentinel. Since Redis always uses the last processed
|
||||
# line as value of a configuration directive, you'd better put includes
|
||||
# at the beginning of this file to avoid overwriting config change at runtime.
|
||||
#
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
################################## NETWORK #####################################
|
||||
|
||||
# By default, if no "bind" configuration directive is specified, KeyDB listens
|
||||
# By default, if no "bind" configuration directive is specified, Redis listens
|
||||
# for connections from all the network interfaces available on the server.
|
||||
# It is possible to listen to just one or multiple selected interfaces using
|
||||
# the "bind" configuration directive, followed by one or more IP addresses.
|
||||
@@ -55,11 +55,11 @@
|
||||
# bind 192.168.1.100 10.0.0.1
|
||||
# bind 127.0.0.1 ::1
|
||||
#
|
||||
# ~~~ WARNING ~~~ If the computer running KeyDB is directly exposed to the
|
||||
# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
|
||||
# internet, binding to all the interfaces is dangerous and will expose the
|
||||
# instance to everybody on the internet. So by default we uncomment the
|
||||
# following bind directive, that will force KeyDB to listen only into
|
||||
# the IPv4 loopback interface address (this means KeyDB will be able to
|
||||
# following bind directive, that will force Redis to listen only into
|
||||
# the IPv4 loopback interface address (this means Redis will be able to
|
||||
# accept connections only from clients running into the same computer it
|
||||
# is running).
|
||||
#
|
||||
@@ -69,7 +69,7 @@
|
||||
bind 127.0.0.1
|
||||
|
||||
# Protected mode is a layer of security protection, in order to avoid that
|
||||
# KeyDB instances left open on the internet are accessed and exploited.
|
||||
# Redis instances left open on the internet are accessed and exploited.
|
||||
#
|
||||
# When protected mode is on and if:
|
||||
#
|
||||
@@ -82,13 +82,13 @@ bind 127.0.0.1
|
||||
# sockets.
|
||||
#
|
||||
# By default protected mode is enabled. You should disable it only if
|
||||
# you are sure you want clients from other hosts to connect to KeyDB
|
||||
# you are sure you want clients from other hosts to connect to Redis
|
||||
# even if no authentication is configured, nor a specific set of interfaces
|
||||
# are explicitly listed using the "bind" directive.
|
||||
protected-mode yes
|
||||
|
||||
# Accept connections on the specified port, default is 6379 (IANA #815344).
|
||||
# If port 0 is specified KeyDB will not listen on a TCP socket.
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
||||
# TCP listen() backlog.
|
||||
@@ -103,10 +103,10 @@ tcp-backlog 511
|
||||
# Unix socket.
|
||||
#
|
||||
# Specify the path for the Unix socket that will be used to listen for
|
||||
# incoming connections. There is no default, so KeyDB will not listen
|
||||
# incoming connections. There is no default, so Redis will not listen
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/keydb.sock
|
||||
# unixsocket /tmp/redis.sock
|
||||
# unixsocketperm 700
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
@@ -126,19 +126,19 @@ timeout 0
|
||||
# On other kernels the period depends on the kernel configuration.
|
||||
#
|
||||
# A reasonable value for this option is 300 seconds, which is the new
|
||||
# KeyDB default starting with Redis 3.2.1.
|
||||
# Redis default starting with Redis 3.2.1.
|
||||
tcp-keepalive 300
|
||||
|
||||
################################# GENERAL #####################################
|
||||
|
||||
# By default KeyDB does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that KeyDB will write a pid file in /var/run/keydb.pid when daemonized.
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||
daemonize no
|
||||
|
||||
# If you run KeyDB from upstart or systemd, KeyDB can interact with your
|
||||
# If you run Redis from upstart or systemd, Redis can interact with your
|
||||
# supervision tree. Options:
|
||||
# supervised no - no supervision interaction
|
||||
# supervised upstart - signal upstart by putting KeyDB into SIGSTOP mode
|
||||
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
|
||||
# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
|
||||
# supervised auto - detect upstart or systemd method based on
|
||||
# UPSTART_JOB or NOTIFY_SOCKET environment variables
|
||||
@@ -146,16 +146,16 @@ daemonize no
|
||||
# They do not enable continuous liveness pings back to your supervisor.
|
||||
supervised no
|
||||
|
||||
# If a pid file is specified, KeyDB writes it where specified at startup
|
||||
# If a pid file is specified, Redis writes it where specified at startup
|
||||
# and removes it at exit.
|
||||
#
|
||||
# When the server runs non daemonized, no pid file is created if none is
|
||||
# specified in the configuration. When the server is daemonized, the pid file
|
||||
# is used even if not specified, defaulting to "/var/run/keydb.pid".
|
||||
# is used even if not specified, defaulting to "/var/run/redis.pid".
|
||||
#
|
||||
# Creating a pid file is best effort: if KeyDB is not able to create it
|
||||
# Creating a pid file is best effort: if Redis is not able to create it
|
||||
# nothing bad happens, the server will start and run normally.
|
||||
pidfile /var/run/keydb_6379.pid
|
||||
pidfile /var/run/redis_6379.pid
|
||||
|
||||
# Specify the server verbosity level.
|
||||
# This can be one of:
|
||||
@@ -166,7 +166,7 @@ pidfile /var/run/keydb_6379.pid
|
||||
loglevel notice
|
||||
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
# KeyDB to log on the standard output. Note that if you use standard
|
||||
# Redis to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile ""
|
||||
|
||||
@@ -175,7 +175,7 @@ logfile ""
|
||||
# syslog-enabled no
|
||||
|
||||
# Specify the syslog identity.
|
||||
# syslog-ident keydb
|
||||
# syslog-ident redis
|
||||
|
||||
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
|
||||
# syslog-facility local0
|
||||
@@ -185,7 +185,7 @@ logfile ""
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
databases 16
|
||||
|
||||
# By default KeyDB shows an ASCII art logo only when started to log to the
|
||||
# By default Redis shows an ASCII art logo only when started to log to the
|
||||
# standard output and if the standard output is a TTY. Basically this means
|
||||
# that normally a logo is displayed only in interactive sessions.
|
||||
#
|
||||
@@ -219,17 +219,17 @@ save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# By default KeyDB will stop accepting writes if RDB snapshots are enabled
|
||||
# By default Redis will stop accepting writes if RDB snapshots are enabled
|
||||
# (at least one save point) and the latest background save failed.
|
||||
# This will make the user aware (in a hard way) that data is not persisting
|
||||
# on disk properly, otherwise chances are that no one will notice and some
|
||||
# disaster will happen.
|
||||
#
|
||||
# If the background saving process will start working again KeyDB will
|
||||
# If the background saving process will start working again Redis will
|
||||
# automatically allow writes again.
|
||||
#
|
||||
# However if you have setup your proper monitoring of the KeyDB server
|
||||
# and persistence, you may want to disable this feature so that KeyDB will
|
||||
# However if you have setup your proper monitoring of the Redis server
|
||||
# and persistence, you may want to disable this feature so that Redis will
|
||||
# continue to work as usual even if there are problems with disk,
|
||||
# permissions, and so forth.
|
||||
stop-writes-on-bgsave-error yes
|
||||
@@ -264,18 +264,18 @@ dir ./
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
# Master-Replica replication. Use replicaof to make a KeyDB instance a copy of
|
||||
# another KeyDB server. A few things to understand ASAP about KeyDB replication.
|
||||
# Master-Replica replication. Use replicaof to make a Redis instance a copy of
|
||||
# another Redis server. A few things to understand ASAP about Redis replication.
|
||||
#
|
||||
# +------------------+ +---------------+
|
||||
# | Master | ---> | Replica |
|
||||
# | (receive writes) | | (exact copy) |
|
||||
# +------------------+ +---------------+
|
||||
#
|
||||
# 1) KeyDB replication is asynchronous, but you can configure a master to
|
||||
# 1) Redis replication is asynchronous, but you can configure a master to
|
||||
# stop accepting writes if it appears to be not connected with at least
|
||||
# a given number of replicas.
|
||||
# 2) KeyDB replicas are able to perform a partial resynchronization with the
|
||||
# 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.
|
||||
@@ -292,7 +292,7 @@ dir ./
|
||||
#
|
||||
# masterauth <master-password>
|
||||
#
|
||||
# However this is not enough if you are using KeyDB ACLs (for Redis version
|
||||
# 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
|
||||
@@ -345,10 +345,10 @@ replica-read-only yes
|
||||
# synchronization". An RDB file is transmitted from the master to the replicas.
|
||||
# The transmission can happen in two different ways:
|
||||
#
|
||||
# 1) Disk-backed: The KeyDB master creates a new process that writes the RDB
|
||||
# 1) Disk-backed: The Redis master creates a new process that writes the RDB
|
||||
# file on disk. Later the file is transferred by the parent
|
||||
# process to the replicas incrementally.
|
||||
# 2) Diskless: The KeyDB master creates a new process that directly writes the
|
||||
# 2) Diskless: The Redis master creates a new process that directly writes the
|
||||
# RDB file to replica sockets, without touching the disk at all.
|
||||
#
|
||||
# With disk-backed replication, while the RDB file is generated, more replicas
|
||||
@@ -397,7 +397,7 @@ repl-diskless-sync-delay 5
|
||||
|
||||
# Disable TCP_NODELAY on the replica socket after SYNC?
|
||||
#
|
||||
# If you select "yes" KeyDB will use a smaller number of TCP packets and
|
||||
# If you select "yes" Redis will use a smaller number of TCP packets and
|
||||
# less bandwidth to send data to replicas. But this can add a delay for
|
||||
# the data to appear on the replica side, up to 40 milliseconds with
|
||||
# Linux kernels using a default configuration.
|
||||
@@ -436,8 +436,8 @@ repl-disable-tcp-nodelay no
|
||||
#
|
||||
# repl-backlog-ttl 3600
|
||||
|
||||
# The replica priority is an integer number published by KeyDB in the INFO output.
|
||||
# It is used by KeyDB Sentinel in order to select a replica to promote into a
|
||||
# The replica priority is an integer number published by Redis in the INFO output.
|
||||
# It is used by Redis Sentinel in order to select a replica to promote into a
|
||||
# master if the master is no longer working correctly.
|
||||
#
|
||||
# A replica with a low priority number is considered better for promotion, so
|
||||
@@ -446,7 +446,7 @@ repl-disable-tcp-nodelay no
|
||||
#
|
||||
# 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
|
||||
# KeyDB Sentinel for promotion.
|
||||
# Redis Sentinel for promotion.
|
||||
#
|
||||
# By default the priority is 100.
|
||||
replica-priority 100
|
||||
@@ -473,10 +473,10 @@ replica-priority 100
|
||||
# By default min-replicas-to-write is set to 0 (feature disabled) and
|
||||
# min-replicas-max-lag is set to 10.
|
||||
|
||||
# A KeyDB master is able to list the address and port of the attached
|
||||
# A Redis master is able to list the address and port of the attached
|
||||
# replicas in different ways. For example the "INFO replication" section
|
||||
# offers this information, which is used, among other tools, by
|
||||
# KeyDB Sentinel in order to discover replica 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.
|
||||
#
|
||||
@@ -504,7 +504,7 @@ replica-priority 100
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Warning: since KeyDB is pretty fast an outside user can try up to
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# 1 million passwords per second against a modern box. This means that you
|
||||
# should use very strong passwords, otherwise they will be very easy to break.
|
||||
# Note that because the password is really a shared secret between the client
|
||||
@@ -512,7 +512,7 @@ replica-priority 100
|
||||
# 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.
|
||||
|
||||
# KeyDB ACL users are defined in the following format:
|
||||
# Redis ACL users are defined in the following format:
|
||||
#
|
||||
# user <username> ... acl rules ...
|
||||
#
|
||||
@@ -539,7 +539,7 @@ replica-priority 100
|
||||
# +@<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 KeyDB command table is described and defined.
|
||||
# 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.
|
||||
@@ -606,9 +606,9 @@ replica-priority 100
|
||||
# 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 keydb.conf to describe users.
|
||||
# format that is used inside redis.conf to describe users.
|
||||
#
|
||||
# aclfile /etc/keydb/users.acl
|
||||
# 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
|
||||
@@ -646,12 +646,12 @@ replica-priority 100
|
||||
################################### CLIENTS ####################################
|
||||
|
||||
# Set the max number of connected clients at the same time. By default
|
||||
# this limit is set to 10000 clients, however if the KeyDB server is not
|
||||
# this limit is set to 10000 clients, however if the Redis server is not
|
||||
# able to configure the process file limit to allow for the specified limit
|
||||
# the max number of allowed clients is set to the current file limit
|
||||
# minus 32 (as KeyDB reserves a few file descriptors for internal uses).
|
||||
# minus 32 (as Redis reserves a few file descriptors for internal uses).
|
||||
#
|
||||
# Once the limit is reached KeyDB will close all the new connections sending
|
||||
# Once the limit is reached Redis will close all the new connections sending
|
||||
# an error 'max number of clients reached'.
|
||||
#
|
||||
# maxclients 10000
|
||||
@@ -659,15 +659,15 @@ replica-priority 100
|
||||
############################## MEMORY MANAGEMENT ################################
|
||||
|
||||
# Set a memory usage limit to the specified amount of bytes.
|
||||
# When the memory limit is reached KeyDB will try to remove keys
|
||||
# When the memory limit is reached Redis will try to remove keys
|
||||
# according to the eviction policy selected (see maxmemory-policy).
|
||||
#
|
||||
# If KeyDB can't remove keys according to the policy, or if the policy is
|
||||
# set to 'noeviction', KeyDB will start to reply with errors to commands
|
||||
# If Redis can't remove keys according to the policy, or if the policy is
|
||||
# set to 'noeviction', Redis will start to reply with errors to commands
|
||||
# that would use more memory, like SET, LPUSH, and so on, and will continue
|
||||
# to reply to read-only commands like GET.
|
||||
#
|
||||
# This option is usually useful when using KeyDB as an LRU or LFU cache, or to
|
||||
# This option is usually useful when using Redis as an LRU or LFU cache, or to
|
||||
# set a hard memory limit for an instance (using the 'noeviction' policy).
|
||||
#
|
||||
# WARNING: If you have replicas attached to an instance with maxmemory on,
|
||||
@@ -683,7 +683,7 @@ replica-priority 100
|
||||
#
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how KeyDB will select what to remove when maxmemory
|
||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||
# is reached. You can select among five behaviors:
|
||||
#
|
||||
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
|
||||
@@ -701,7 +701,7 @@ replica-priority 100
|
||||
# Both LRU, LFU and volatile-ttl are implemented using approximated
|
||||
# randomized algorithms.
|
||||
#
|
||||
# Note: with any of the above policies, KeyDB will return an error on write
|
||||
# Note: with any of the above policies, Redis will return an error on write
|
||||
# operations, when there are no suitable keys for eviction.
|
||||
#
|
||||
# At the date of writing these commands are: set setnx setex append
|
||||
@@ -716,7 +716,7 @@ replica-priority 100
|
||||
|
||||
# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can tune it for speed or
|
||||
# accuracy. For default KeyDB will check five keys and pick the one that was
|
||||
# accuracy. For default Redis will check five keys and pick the one that was
|
||||
# used less recently, you can change the sample size using the following
|
||||
# configuration directive.
|
||||
#
|
||||
@@ -747,16 +747,16 @@ replica-priority 100
|
||||
|
||||
############################# LAZY FREEING ####################################
|
||||
|
||||
# KeyDB has two primitives to delete keys. One is called DEL and is a blocking
|
||||
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
||||
# deletion of the object. It means that the server stops processing new commands
|
||||
# in order to reclaim all the memory associated with an object in a synchronous
|
||||
# way. If the key deleted is associated with a small object, the time needed
|
||||
# in order to execute the DEL command is very small and comparable to most other
|
||||
# O(1) or O(log_N) commands in KeyDB. However if the key is associated with an
|
||||
# O(1) or O(log_N) commands in Redis. However if the key is associated with an
|
||||
# aggregated value containing millions of elements, the server can block for
|
||||
# a long time (even seconds) in order to complete the operation.
|
||||
#
|
||||
# For the above reasons KeyDB also offers non blocking deletion primitives
|
||||
# For the above reasons Redis also offers non blocking deletion primitives
|
||||
# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and
|
||||
# FLUSHDB commands, in order to reclaim memory in background. Those commands
|
||||
# are executed in constant time. Another thread will incrementally free the
|
||||
@@ -764,9 +764,9 @@ replica-priority 100
|
||||
#
|
||||
# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled.
|
||||
# It's up to the design of the application to understand when it is a good
|
||||
# idea to use one or the other. However the KeyDB server sometimes has to
|
||||
# idea to use one or the other. However the Redis server sometimes has to
|
||||
# delete keys or flush the whole database as a side effect of other operations.
|
||||
# Specifically KeyDB deletes objects independently of a user call in the
|
||||
# Specifically Redis deletes objects independently of a user call in the
|
||||
# following scenarios:
|
||||
#
|
||||
# 1) On eviction, because of the maxmemory and maxmemory policy configurations,
|
||||
@@ -796,20 +796,20 @@ replica-lazy-flush no
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
# By default KeyDB asynchronously dumps the dataset on disk. This mode is
|
||||
# good enough in many applications, but an issue with the KeyDB process or
|
||||
# By default Redis asynchronously dumps the dataset on disk. This mode is
|
||||
# good enough in many applications, but an issue with the Redis process or
|
||||
# a power outage may result into a few minutes of writes lost (depending on
|
||||
# the configured save points).
|
||||
#
|
||||
# The Append Only File is an alternative persistence mode that provides
|
||||
# much better durability. For instance using the default data fsync policy
|
||||
# (see later in the config file) KeyDB can lose just one second of writes in a
|
||||
# (see later in the config file) Redis can lose just one second of writes in a
|
||||
# dramatic event like a server power outage, or a single write if something
|
||||
# wrong with the KeyDB process itself happens, but the operating system is
|
||||
# wrong with the Redis process itself happens, but the operating system is
|
||||
# still running correctly.
|
||||
#
|
||||
# AOF and RDB persistence can be enabled at the same time without problems.
|
||||
# If the AOF is enabled on startup KeyDB will load the AOF, that is the file
|
||||
# If the AOF is enabled on startup Redis will load the AOF, that is the file
|
||||
# with the better durability guarantees.
|
||||
#
|
||||
# Please check http://redis.io/topics/persistence for more information.
|
||||
@@ -824,7 +824,7 @@ appendfilename "appendonly.aof"
|
||||
# instead of waiting for more data in the output buffer. Some OS will really flush
|
||||
# data on disk, some other OS will just try to do it ASAP.
|
||||
#
|
||||
# KeyDB supports three different modes:
|
||||
# Redis supports three different modes:
|
||||
#
|
||||
# no: don't fsync, just let the OS flush the data when it wants. Faster.
|
||||
# always: fsync after every write to the append only log. Slow, Safest.
|
||||
@@ -850,7 +850,7 @@ appendfsync everysec
|
||||
# When the AOF fsync policy is set to always or everysec, and a background
|
||||
# saving process (a background save or AOF log background rewriting) is
|
||||
# performing a lot of I/O against the disk, in some Linux configurations
|
||||
# KeyDB may block too long on the fsync() call. Note that there is no fix for
|
||||
# Redis may block too long on the fsync() call. Note that there is no fix for
|
||||
# this currently, as even performing fsync in a different thread will block
|
||||
# our synchronous write(2) call.
|
||||
#
|
||||
@@ -858,7 +858,7 @@ appendfsync everysec
|
||||
# that will prevent fsync() from being called in the main process while a
|
||||
# BGSAVE or BGREWRITEAOF is in progress.
|
||||
#
|
||||
# This means that while another child is saving, the durability of KeyDB is
|
||||
# This means that while another child is saving, the durability of Redis is
|
||||
# the same as "appendfsync none". In practical terms, this means that it is
|
||||
# possible to lose up to 30 seconds of log in the worst scenario (with the
|
||||
# default Linux settings).
|
||||
@@ -869,10 +869,10 @@ appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# Automatic rewrite of the append only file.
|
||||
# KeyDB is able to automatically rewrite the log file implicitly calling
|
||||
# Redis is able to automatically rewrite the log file implicitly calling
|
||||
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
|
||||
#
|
||||
# This is how it works: KeyDB remembers the size of the AOF file after the
|
||||
# This is how it works: Redis remembers the size of the AOF file after the
|
||||
# latest rewrite (if no rewrite has happened since the restart, the size of
|
||||
# the AOF at startup is used).
|
||||
#
|
||||
@@ -888,19 +888,19 @@ no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# An AOF file may be found to be truncated at the end during the KeyDB
|
||||
# An AOF file may be found to be truncated at the end during the Redis
|
||||
# startup process, when the AOF data gets loaded back into memory.
|
||||
# This may happen when the system where KeyDB is running
|
||||
# This may happen when the system where Redis is running
|
||||
# crashes, especially when an ext4 filesystem is mounted without the
|
||||
# data=ordered option (however this can't happen when KeyDB itself
|
||||
# data=ordered option (however this can't happen when Redis itself
|
||||
# crashes or aborts but the operating system still works correctly).
|
||||
#
|
||||
# KeyDB can either exit with an error when this happens, or load as much
|
||||
# Redis can either exit with an error when this happens, or load as much
|
||||
# data as possible (the default now) and start if the AOF file is found
|
||||
# to be truncated at the end. The following option controls this behavior.
|
||||
#
|
||||
# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
|
||||
# the KeyDB server starts emitting a log to inform the user of the event.
|
||||
# the Redis server starts emitting a log to inform the user of the event.
|
||||
# Otherwise if the option is set to no, the server aborts with an error
|
||||
# and refuses to start. When the option is set to no, the user requires
|
||||
# to fix the AOF file using the "keydb-check-aof" utility before to restart
|
||||
@@ -908,17 +908,17 @@ auto-aof-rewrite-min-size 64mb
|
||||
#
|
||||
# Note that if the AOF file will be found to be corrupted in the middle
|
||||
# the server will still exit with an error. This option only applies when
|
||||
# KeyDB will try to read more data from the AOF file but not enough bytes
|
||||
# Redis will try to read more data from the AOF file but not enough bytes
|
||||
# will be found.
|
||||
aof-load-truncated yes
|
||||
|
||||
# When rewriting the AOF file, KeyDB is able to use an RDB preamble in the
|
||||
# When rewriting the AOF file, Redis is able to use an RDB preamble in the
|
||||
# AOF file for faster rewrites and recoveries. When this option is turned
|
||||
# on the rewritten AOF file is composed of two different stanzas:
|
||||
#
|
||||
# [RDB file][AOF tail]
|
||||
#
|
||||
# When loading KeyDB recognizes that the AOF file starts with the "REDIS"
|
||||
# When loading Redis recognizes that the AOF file starts with the "REDIS"
|
||||
# string and loads the prefixed RDB file, and continues loading the AOF
|
||||
# tail.
|
||||
aof-use-rdb-preamble yes
|
||||
@@ -927,7 +927,7 @@ aof-use-rdb-preamble yes
|
||||
|
||||
# Max execution time of a Lua script in milliseconds.
|
||||
#
|
||||
# If the maximum execution time is reached KeyDB will log that a script is
|
||||
# If the maximum execution time is reached Redis will log that a script is
|
||||
# still in execution after the maximum allowed time and will start to
|
||||
# reply to queries with an error.
|
||||
#
|
||||
@@ -941,17 +941,17 @@ aof-use-rdb-preamble yes
|
||||
# Set it to 0 or a negative value for unlimited execution without warnings.
|
||||
lua-time-limit 5000
|
||||
|
||||
################################ KEYDB CLUSTER ###############################
|
||||
################################ REDIS CLUSTER ###############################
|
||||
|
||||
# Normal KeyDB instances can't be part of a KeyDB Cluster; only nodes that are
|
||||
# started as cluster nodes can. In order to start a KeyDB instance as a
|
||||
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
|
||||
# started as cluster nodes can. In order to start a Redis instance as a
|
||||
# cluster node enable the cluster support uncommenting the following:
|
||||
#
|
||||
# cluster-enabled yes
|
||||
|
||||
# Every cluster node has a cluster configuration file. This file is not
|
||||
# intended to be edited by hand. It is created and updated by KeyDB nodes.
|
||||
# Every KeyDB Cluster node requires a different cluster configuration file.
|
||||
# intended to be edited by hand. It is created and updated by Redis nodes.
|
||||
# Every Redis Cluster node requires a different cluster configuration file.
|
||||
# Make sure that instances running in the same system do not have
|
||||
# overlapping cluster configuration file names.
|
||||
#
|
||||
@@ -1027,7 +1027,7 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-migration-barrier 1
|
||||
|
||||
# By default KeyDB Cluster nodes stop accepting queries if they detect there
|
||||
# By default Redis Cluster nodes stop accepting queries if they detect there
|
||||
# is at least an hash slot uncovered (no available node is serving it).
|
||||
# This way if the cluster is partially down (for example a range of hash slots
|
||||
# are no longer covered) all the cluster becomes, eventually, unavailable.
|
||||
@@ -1055,11 +1055,11 @@ lua-time-limit 5000
|
||||
|
||||
########################## CLUSTER DOCKER/NAT support ########################
|
||||
|
||||
# In certain deployments, KeyDB Cluster nodes address discovery fails, because
|
||||
# In certain deployments, Redis Cluster nodes address discovery fails, because
|
||||
# addresses are NAT-ted or because ports are forwarded (the typical case is
|
||||
# Docker and other containers).
|
||||
#
|
||||
# In order to make KeyDB Cluster working in such environments, a static
|
||||
# In order to make Redis Cluster working in such environments, a static
|
||||
# configuration where each node knows its public address is needed. The
|
||||
# following two options are used for this scope, and are:
|
||||
#
|
||||
@@ -1072,7 +1072,7 @@ lua-time-limit 5000
|
||||
# so that other nodes will be able to correctly map the address of the node
|
||||
# publishing the information.
|
||||
#
|
||||
# If the above options are not used, the normal KeyDB Cluster auto-detection
|
||||
# If the above options are not used, the normal Redis Cluster auto-detection
|
||||
# will be used instead.
|
||||
#
|
||||
# Note that when remapped, the bus port may not be at the fixed offset of
|
||||
@@ -1088,14 +1088,14 @@ lua-time-limit 5000
|
||||
|
||||
################################## SLOW LOG ###################################
|
||||
|
||||
# The KeyDB Slow Log is a system to log queries that exceeded a specified
|
||||
# The Redis Slow Log is a system to log queries that exceeded a specified
|
||||
# execution time. The execution time does not include the I/O operations
|
||||
# like talking with the client, sending the reply and so forth,
|
||||
# but just the time needed to actually execute the command (this is the only
|
||||
# stage of command execution where the thread is blocked and can not serve
|
||||
# other requests in the meantime).
|
||||
#
|
||||
# You can configure the slow log with two parameters: one tells KeyDB
|
||||
# You can configure the slow log with two parameters: one tells Redis
|
||||
# what is the execution time, in microseconds, to exceed in order for the
|
||||
# command to get logged, and the other parameter is the length of the
|
||||
# slow log. When a new command is logged the oldest one is removed from the
|
||||
@@ -1112,9 +1112,9 @@ slowlog-max-len 128
|
||||
|
||||
################################ LATENCY MONITOR ##############################
|
||||
|
||||
# The KeyDB latency monitoring subsystem samples different operations
|
||||
# The Redis latency monitoring subsystem samples different operations
|
||||
# at runtime in order to collect data related to possible sources of
|
||||
# latency of a KeyDB instance.
|
||||
# latency of a Redis instance.
|
||||
#
|
||||
# Via the LATENCY command this information is available to the user that can
|
||||
# print graphs and obtain reports.
|
||||
@@ -1133,7 +1133,7 @@ latency-monitor-threshold 0
|
||||
|
||||
############################# EVENT NOTIFICATION ##############################
|
||||
|
||||
# KeyDB can notify Pub/Sub clients about events happening in the key space.
|
||||
# Redis can notify Pub/Sub clients about events happening in the key space.
|
||||
# This feature is documented at http://redis.io/topics/notifications
|
||||
#
|
||||
# For instance if keyspace events notification is enabled, and a client
|
||||
@@ -1143,7 +1143,7 @@ latency-monitor-threshold 0
|
||||
# PUBLISH __keyspace@0__:foo del
|
||||
# PUBLISH __keyevent@0__:del foo
|
||||
#
|
||||
# It is possible to select the events that KeyDB will notify among a set
|
||||
# It is possible to select the events that Redis will notify among a set
|
||||
# of classes. Every class is identified by a single character:
|
||||
#
|
||||
# K Keyspace events, published with __keyspace@<db>__ prefix.
|
||||
@@ -1179,12 +1179,12 @@ notify-keyspace-events ""
|
||||
|
||||
############################### GOPHER SERVER #################################
|
||||
|
||||
# KeyDB contains an implementation of the Gopher protocol, as specified in
|
||||
# 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 KeyDB server has just 100 lines of code in order to implement this
|
||||
# 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
|
||||
@@ -1194,18 +1194,18 @@ notify-keyspace-events ""
|
||||
# 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 KeyDB, we gave it the Gopher protocol
|
||||
# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol
|
||||
# as a gift.
|
||||
#
|
||||
# --- HOW IT WORKS? ---
|
||||
#
|
||||
# The KeyDB Gopher support uses the inline protocol of KeyDB, and specifically
|
||||
# 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 KeyDB commands starting
|
||||
# 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 KeyDB when Gopher is enabled and send it
|
||||
# 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.
|
||||
#
|
||||
@@ -1216,7 +1216,7 @@ notify-keyspace-events ""
|
||||
#
|
||||
# --- SECURITY WARNING ---
|
||||
#
|
||||
# If you plan to put KeyDB on the internet in a publicly accessible address
|
||||
# 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:
|
||||
#
|
||||
@@ -1310,8 +1310,8 @@ stream-node-max-bytes 4096
|
||||
stream-node-max-entries 100
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main KeyDB hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation KeyDB uses (see dict.c)
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
# performs a lazy rehashing: the more operation you run into a hash table
|
||||
# that is rehashing, the more rehashing "steps" are performed, so if the
|
||||
# server is idle the rehashing is never complete and some more memory is used
|
||||
@@ -1322,7 +1322,7 @@ stream-node-max-entries 100
|
||||
#
|
||||
# If unsure:
|
||||
# use "activerehashing no" if you have hard latency requirements and it is
|
||||
# not a good thing in your environment that KeyDB can reply from time to time
|
||||
# not a good thing in your environment that Redis can reply from time to time
|
||||
# to queries with 2 milliseconds delay.
|
||||
#
|
||||
# use "activerehashing yes" if you don't have such hard requirements but
|
||||
@@ -1374,21 +1374,21 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
#
|
||||
# client-query-buffer-limit 1gb
|
||||
|
||||
# In the KeyDB protocol, bulk requests, that are, elements representing single
|
||||
# In the Redis protocol, bulk requests, that are, elements representing single
|
||||
# strings, are normally limited ot 512 mb. However you can change this limit
|
||||
# here.
|
||||
#
|
||||
# proto-max-bulk-len 512mb
|
||||
|
||||
# KeyDB calls an internal function to perform many background tasks, like
|
||||
# Redis calls an internal function to perform many background tasks, like
|
||||
# closing connections of clients in timeout, purging expired keys that are
|
||||
# never requested, and so forth.
|
||||
#
|
||||
# Not all tasks are performed with the same frequency, but KeyDB checks for
|
||||
# Not all tasks are performed with the same frequency, but Redis checks for
|
||||
# tasks to perform according to the specified "hz" value.
|
||||
#
|
||||
# By default "hz" is set to 10. Raising the value will use more CPU when
|
||||
# KeyDB is idle, but at the same time will make KeyDB more responsive when
|
||||
# Redis is idle, but at the same time will make Redis more responsive when
|
||||
# there are many keys expiring at the same time, and timeouts may be
|
||||
# handled with more precision.
|
||||
#
|
||||
@@ -1402,7 +1402,7 @@ hz 10
|
||||
# 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, KeyDB
|
||||
# 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.
|
||||
#
|
||||
@@ -1419,22 +1419,22 @@ dynamic-hz yes
|
||||
# big latency spikes.
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
# When KeyDB saves RDB file, if the following option is enabled
|
||||
# When redis saves RDB 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
|
||||
# big latency spikes.
|
||||
rdb-save-incremental-fsync yes
|
||||
|
||||
# KeyDB LFU eviction (see maxmemory setting) can be tuned. However it is a good
|
||||
# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good
|
||||
# idea to start with the default settings and only change them after investigating
|
||||
# how to improve the performances and how the keys LFU change over time, which
|
||||
# is possible to inspect via the OBJECT FREQ command.
|
||||
#
|
||||
# There are two tunable parameters in the KeyDB LFU implementation: the
|
||||
# There are two tunable parameters in the Redis LFU implementation: the
|
||||
# counter logarithm factor and the counter decay time. It is important to
|
||||
# understand what the two parameters mean before changing them.
|
||||
#
|
||||
# The LFU counter is just 8 bits per key, it's maximum value is 255, so KeyDB
|
||||
# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis
|
||||
# uses a probabilistic increment with logarithmic behavior. Given the value
|
||||
# of the old counter, when a key is accessed, the counter is incremented in
|
||||
# this way:
|
||||
@@ -1486,7 +1486,7 @@ rdb-save-incremental-fsync yes
|
||||
# What is active defragmentation?
|
||||
# -------------------------------
|
||||
#
|
||||
# Active (online) defragmentation allows a KeyDB server to compact the
|
||||
# Active (online) defragmentation allows a Redis server to compact the
|
||||
# spaces left between small allocations and deallocations of data in memory,
|
||||
# thus allowing to reclaim back memory.
|
||||
#
|
||||
@@ -1498,7 +1498,7 @@ rdb-save-incremental-fsync yes
|
||||
# in an "hot" way, while the server is running.
|
||||
#
|
||||
# Basically when the fragmentation is over a certain level (see the
|
||||
# configuration options below) KeyDB will start to create new copies of the
|
||||
# configuration options below) Redis will start to create new copies of the
|
||||
# values in contiguous memory regions by exploiting certain specific Jemalloc
|
||||
# features (in order to understand if an allocation is causing fragmentation
|
||||
# and to allocate it in a better place), and at the same time, will release the
|
||||
@@ -1507,8 +1507,8 @@ rdb-save-incremental-fsync yes
|
||||
#
|
||||
# Important things to understand:
|
||||
#
|
||||
# 1. This feature is disabled by default, and only works if you compiled KeyDB
|
||||
# to use the copy of Jemalloc we ship with the source code of KeyDB.
|
||||
# 1. This feature is disabled by default, and only works if you compiled Redis
|
||||
# to use the copy of Jemalloc we ship with the source code of Redis.
|
||||
# This is the default with Linux builds.
|
||||
#
|
||||
# 2. You never need to enable this feature if you don't have fragmentation
|
||||
@@ -1561,10 +1561,3 @@ server-threads 2
|
||||
# replicas will still sync in the normal way and incorrect ordering when
|
||||
# bringing up replicas can result in data loss (the first master will win).
|
||||
# active-replica yes
|
||||
|
||||
# Enable Pro? KeyDB pro provides support for pro only features
|
||||
# note: you may omit the license key to demo pro features for a limited time
|
||||
# enable-pro [License Key]
|
||||
|
||||
# Enable FLASH support? (Pro Only)
|
||||
# storage-provider flash /path/to/flash/db
|
||||
+14
-28
@@ -47,6 +47,8 @@ endif
|
||||
USEASM?=true
|
||||
|
||||
ifneq ($(SANITIZE),)
|
||||
CC=clang
|
||||
CXX=clang++
|
||||
CFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
|
||||
CXXFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
|
||||
LDFLAGS+= -fsanitize=$(SANITIZE)
|
||||
@@ -74,11 +76,9 @@ endif
|
||||
# To get ARM stack traces if Redis crashes we need a special C flag.
|
||||
ifneq (,$(filter aarch64 armv,$(uname_M)))
|
||||
CFLAGS+=-funwind-tables
|
||||
CXXFLAGS+=-funwind-tables
|
||||
else
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
CFLAGS+=-funwind-tables
|
||||
CXXFLAGS+=-funwind-tables
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -103,24 +103,22 @@ endif
|
||||
-include .make-settings
|
||||
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(CXXFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm -lcurl
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
ifneq ($(@@),32bit)
|
||||
CFLAGS+= -m64
|
||||
CXXFLAGS+= -m64
|
||||
LDFLAGS+= -m64
|
||||
endif
|
||||
DEBUG=-g
|
||||
DEBUG_FLAGS=-g
|
||||
export CFLAGS CXXFLAGS LDFLAGS DEBUG DEBUG_FLAGS
|
||||
export CFLAGS LDFLAGS DEBUG DEBUG_FLAGS
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_CXXFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread -lrt
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
@@ -137,7 +135,6 @@ ifeq ($(uname_S),OpenBSD)
|
||||
FINAL_LIBS+= -lpthread
|
||||
ifeq ($(USE_BACKTRACE),yes)
|
||||
FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
|
||||
FINAL_CXXFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
|
||||
FINAL_LDFLAGS+= -L/usr/local/lib
|
||||
FINAL_LIBS+= -lexecinfo
|
||||
endif
|
||||
@@ -155,7 +152,6 @@ else
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+=-ldl -pthread -lrt -luuid
|
||||
FINAL_CFLAGS += -DMOTD
|
||||
FINAL_CXXFLAGS += -DMOTD
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -193,8 +189,8 @@ ifeq ($(MALLOC),memkind)
|
||||
endif
|
||||
|
||||
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
||||
REDIS_CXX=$(QUIET_CC)$(CXX) $(FINAL_CXXFLAGS)
|
||||
KEYDB_AS=$(QUIET_CC) as --64 -g
|
||||
REDIS_CXX=$(QUIET_CC)$(CC) $(FINAL_CXXFLAGS)
|
||||
REDIS_NASM=$(QUIET_CC)nasm -felf64
|
||||
REDIS_LD=$(QUIET_LINK)$(CXX) $(FINAL_LDFLAGS)
|
||||
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
|
||||
|
||||
@@ -213,9 +209,9 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=keydb-server
|
||||
REDIS_SENTINEL_NAME=keydb-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o motd.o $(ASM_OBJ)
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o $(ASM_OBJ)
|
||||
REDIS_CLI_NAME=keydb-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o $(ASM_OBJ)
|
||||
REDIS_BENCHMARK_NAME=keydb-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ)
|
||||
REDIS_CHECK_RDB_NAME=keydb-check-rdb
|
||||
@@ -241,13 +237,10 @@ persist-settings: distclean
|
||||
echo OPT=$(OPT) >> .make-settings
|
||||
echo MALLOC=$(MALLOC) >> .make-settings
|
||||
echo CFLAGS=$(CFLAGS) >> .make-settings
|
||||
echo CXXFLAGS=$(CXXFLAGS) >> .make-settings
|
||||
echo LDFLAGS=$(LDFLAGS) >> .make-settings
|
||||
echo REDIS_CFLAGS=$(REDIS_CFLAGS) >> .make-settings
|
||||
echo REDIS_CXXFLAGS=$(REDIS_CXXFLAGS) >> .make-settings
|
||||
echo REDIS_LDFLAGS=$(REDIS_LDFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_CFLAGS=$(FINAL_CFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_CXXFLAGS=$(FINAL_CXXFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_LDFLAGS=$(FINAL_LDFLAGS) >> .make-settings
|
||||
-(cd modules && $(MAKE))
|
||||
-(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS))
|
||||
@@ -258,8 +251,6 @@ persist-settings: distclean
|
||||
# Clean everything, persist settings and build dependencies if anything changed
|
||||
ifneq ($(strip $(PREV_FINAL_CFLAGS)), $(strip $(FINAL_CFLAGS)))
|
||||
.make-prerequisites: persist-settings
|
||||
else ifneq ($(strip $(PREV_FINAL_CXXFLAGS)), $(strip $(FINAL_CXXFLAGS)))
|
||||
.make-prerequisites: persist-settings
|
||||
else ifneq ($(strip $(PREV_FINAL_LDFLAGS)), $(strip $(FINAL_LDFLAGS)))
|
||||
.make-prerequisites: persist-settings
|
||||
else
|
||||
@@ -285,7 +276,7 @@ $(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME)
|
||||
|
||||
# keydb-cli
|
||||
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS) -lcurl
|
||||
|
||||
# keydb-benchmark
|
||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
@@ -294,25 +285,20 @@ $(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c
|
||||
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
||||
|
||||
|
||||
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
||||
-include $(DEP)
|
||||
|
||||
# Because the jemalloc.h header is generated as a part of the jemalloc build,
|
||||
# building it should complete before building any other object. Instead of
|
||||
# depending on a single artifact, build all dependencies first.
|
||||
%.o: %.c .make-prerequisites
|
||||
$(REDIS_CC) -MMD -c $<
|
||||
$(REDIS_CC) -c $<
|
||||
|
||||
%.o: %.cpp .make-prerequisites
|
||||
$(REDIS_CXX) -MMD -c $<
|
||||
$(REDIS_CXX) -c $<
|
||||
|
||||
%.o: %.asm .make-prerequisites
|
||||
$(KEYDB_AS) $< -o $@
|
||||
$(REDIS_NASM) $<
|
||||
|
||||
clean:
|
||||
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark
|
||||
rm -f $(DEP)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
@@ -350,7 +336,7 @@ bench: $(REDIS_BENCHMARK_NAME)
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
$(MAKE) CXXFLAGS="-m32" CFLAGS="-m32" LDFLAGS="-m32"
|
||||
$(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
|
||||
|
||||
gcov:
|
||||
$(MAKE) REDIS_CFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" REDIS_LDFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
+4
-4
@@ -1123,7 +1123,7 @@ int ACLLoadConfiguredUsers(void) {
|
||||
|
||||
/* This function loads the ACL from the specified filename: every line
|
||||
* is validated and should be either empty or in the format used to specify
|
||||
* users in the keydb.conf configuration or in the ACL file, that is:
|
||||
* users in the redis.conf configuration or in the ACL file, that is:
|
||||
*
|
||||
* user <username> ... rules ...
|
||||
*
|
||||
@@ -1347,17 +1347,17 @@ cleanup:
|
||||
|
||||
/* This function is called once the server is already running, modules are
|
||||
* loaded, and we are ready to start, in order to load the ACLs either from
|
||||
* the pending list of users defined in keydb.conf, or from the ACL file.
|
||||
* the pending list of users defined in redis.conf, or from the ACL file.
|
||||
* The function will just exit with an error if the user is trying to mix
|
||||
* both the loading methods. */
|
||||
void ACLLoadUsersAtStartup(void) {
|
||||
if (g_pserver->acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) {
|
||||
serverLog(LL_WARNING,
|
||||
"Configuring KeyDB with users defined in keydb.conf and at "
|
||||
"Configuring Redis with users defined in redis.conf and at "
|
||||
"the same setting an ACL file path is invalid. This setup "
|
||||
"is very likely to lead to configuration errors and security "
|
||||
"holes, please define either an ACL file or declare users "
|
||||
"directly in your keydb.conf, but not both.");
|
||||
"directly in your redis.conf, but not both.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
+10
-24
@@ -80,11 +80,11 @@ public:
|
||||
mutex_wrapper g_lock;
|
||||
|
||||
#else
|
||||
fastlock g_lock("AE (global)");
|
||||
fastlock g_lock;
|
||||
#endif
|
||||
thread_local aeEventLoop *g_eventLoopThisThread = NULL;
|
||||
|
||||
#define AE_ASSERT(x) if (!(x)) do { fprintf(stderr, "AE_ASSERT FAILURE %s: %d\n", __FILE__, __LINE__); *((volatile int*)1) = 1; } while(0)
|
||||
#define AE_ASSERT(x) if (!(x)) do { fprintf(stderr, "AE_ASSER FAILURE\n"); *((volatile int*)0) = 1; } while(0)
|
||||
|
||||
/* Include the best multiplexing layer supported by this system.
|
||||
* The following should be ordered by performances, descending. */
|
||||
@@ -237,11 +237,11 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
cmd.clientData = clientData;
|
||||
cmd.pctl = nullptr;
|
||||
if (fSynchronous)
|
||||
{
|
||||
cmd.pctl = new (MALLOC_LOCAL) aeCommandControl();
|
||||
cmd.pctl->mutexcv.lock();
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
if (fSynchronous)
|
||||
cmd.pctl->mutexcv.lock();
|
||||
auto size = safe_write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
if (size != sizeof(cmd))
|
||||
{
|
||||
@@ -252,7 +252,6 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
|
||||
if (fSynchronous)
|
||||
{
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
cmd.pctl->cv.wait(ulock);
|
||||
ret = cmd.pctl->rval;
|
||||
delete cmd.pctl;
|
||||
@@ -273,8 +272,7 @@ int aePostFunction(aeEventLoop *eventLoop, aePostFunctionProc *proc, void *arg)
|
||||
cmd.proc = proc;
|
||||
cmd.clientData = arg;
|
||||
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
if (size != sizeof(cmd))
|
||||
return AE_ERR;
|
||||
AE_ASSERT(size == sizeof(cmd));
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
@@ -291,19 +289,15 @@ int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynch
|
||||
cmd.pfn = new (MALLOC_LOCAL) std::function<void()>(fn);
|
||||
cmd.pctl = nullptr;
|
||||
if (fSynchronous)
|
||||
{
|
||||
cmd.pctl = new (MALLOC_LOCAL) aeCommandControl();
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
if (fSynchronous)
|
||||
cmd.pctl->mutexcv.lock();
|
||||
}
|
||||
|
||||
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
if (size != sizeof(cmd))
|
||||
return AE_ERR;
|
||||
AE_ASSERT(size == sizeof(cmd));
|
||||
int ret = AE_OK;
|
||||
if (fSynchronous)
|
||||
{
|
||||
std::unique_lock<std::mutex> ulock(cmd.pctl->mutexcv, std::defer_lock);
|
||||
cmd.pctl->cv.wait(ulock);
|
||||
ret = cmd.pctl->rval;
|
||||
delete cmd.pctl;
|
||||
@@ -333,7 +327,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
for (i = 0; i < setsize; i++)
|
||||
eventLoop->events[i].mask = AE_NONE;
|
||||
|
||||
fastlock_init(&eventLoop->flock, "event loop");
|
||||
fastlock_init(&eventLoop->flock);
|
||||
int rgfd[2];
|
||||
if (pipe(rgfd) < 0)
|
||||
goto err;
|
||||
@@ -390,18 +384,10 @@ extern "C" void aeDeleteEventLoop(aeEventLoop *eventLoop) {
|
||||
aeApiFree(eventLoop);
|
||||
zfree(eventLoop->events);
|
||||
zfree(eventLoop->fired);
|
||||
zfree(eventLoop);
|
||||
fastlock_free(&eventLoop->flock);
|
||||
close(eventLoop->fdCmdRead);
|
||||
close(eventLoop->fdCmdWrite);
|
||||
|
||||
auto *te = eventLoop->timeEventHead;
|
||||
while (te)
|
||||
{
|
||||
auto *teNext = te->next;
|
||||
zfree(te);
|
||||
te = teNext;
|
||||
}
|
||||
zfree(eventLoop);
|
||||
}
|
||||
|
||||
extern "C" void aeStop(aeEventLoop *eventLoop) {
|
||||
|
||||
+4
-25
@@ -124,21 +124,6 @@ void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
}
|
||||
|
||||
void installAofRewriteEvent()
|
||||
{
|
||||
serverTL->fRetrySetAofEvent = false;
|
||||
if (!g_pserver->aof_rewrite_pending) {
|
||||
g_pserver->aof_rewrite_pending = true;
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [] {
|
||||
g_pserver->aof_rewrite_pending = false;
|
||||
if (g_pserver->aof_pipe_write_data_to_child >= 0)
|
||||
aeCreateFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->aof_pipe_write_data_to_child, AE_WRITABLE, aofChildWriteDiffData, NULL);
|
||||
});
|
||||
if (res != AE_OK)
|
||||
serverTL->fRetrySetAofEvent = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Append data to the AOF rewrite buffer, allocating new blocks if needed. */
|
||||
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
listNode *ln = listLast(g_pserver->aof_rewrite_buf_blocks);
|
||||
@@ -180,7 +165,7 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
|
||||
/* Install a file event to send data to the rewrite child if there is
|
||||
* not one already. */
|
||||
installAofRewriteEvent();
|
||||
aeCreateRemoteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->aof_pipe_write_data_to_child, AE_WRITABLE, aofChildWriteDiffData, NULL, FALSE);
|
||||
}
|
||||
|
||||
/* Write the buffer (possibly composed of multiple blocks) into the specified
|
||||
@@ -356,9 +341,6 @@ void flushAppendOnlyFile(int force) {
|
||||
int sync_in_progress = 0;
|
||||
mstime_t latency;
|
||||
|
||||
if (serverTL->fRetrySetAofEvent)
|
||||
installAofRewriteEvent();
|
||||
|
||||
if (sdslen(g_pserver->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
|
||||
@@ -696,7 +678,7 @@ client *createFakeClient(void) {
|
||||
c->puser = NULL;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
fastlock_init(&c->lock, "fake client");
|
||||
fastlock_init(&c->lock);
|
||||
fastlock_lock(&c->lock);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
@@ -1574,19 +1556,16 @@ error:
|
||||
|
||||
void aofClosePipes(void) {
|
||||
int fdAofAckPipe = g_pserver->aof_pipe_read_ack_from_child;
|
||||
int res = aePostFunction(g_pserver->el_alf_pip_read_ack_from_child, [fdAofAckPipe]{
|
||||
aePostFunction(g_pserver->el_alf_pip_read_ack_from_child, [fdAofAckPipe]{
|
||||
aeDeleteFileEventAsync(serverTL->el,fdAofAckPipe,AE_READABLE);
|
||||
close (fdAofAckPipe);
|
||||
});
|
||||
serverAssert(res == AE_OK);
|
||||
|
||||
int fdAofWritePipe = g_pserver->aof_pipe_write_data_to_child;
|
||||
res = aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fdAofWritePipe]{
|
||||
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fdAofWritePipe]{
|
||||
aeDeleteFileEventAsync(serverTL->el,fdAofWritePipe,AE_WRITABLE);
|
||||
close(fdAofWritePipe);
|
||||
});
|
||||
serverAssert(res == AE_OK);
|
||||
g_pserver->aof_pipe_write_data_to_child = -1;
|
||||
|
||||
close(g_pserver->aof_pipe_read_data_from_parent);
|
||||
close(g_pserver->aof_pipe_write_ack_to_parent);
|
||||
|
||||
+3
-2
@@ -36,6 +36,7 @@ const char *ascii_logo =
|
||||
" Port: %d\n"
|
||||
" PID: %ld\n"
|
||||
" \n"
|
||||
" %s\n"
|
||||
" Like KeyDB? Star us on GitHub! \n"
|
||||
" \n"
|
||||
" \n";
|
||||
" https://github.com/JohnSully/KeyDB \n"
|
||||
" \n\n";
|
||||
+42
-97
@@ -295,15 +295,6 @@ int clusterLoadConfig(char *filename) {
|
||||
if (clusterGetMaxEpoch() > g_pserver->cluster->currentEpoch) {
|
||||
g_pserver->cluster->currentEpoch = clusterGetMaxEpoch();
|
||||
}
|
||||
|
||||
if (dictSize(g_pserver->cluster->nodes) > 1 && cserver.thread_min_client_threshold < 100)
|
||||
{
|
||||
// Because we expect the individual load of a client to be much less in a cluster (it will spread over multiple server)
|
||||
// we can increase the grouping of clients on a single thread within reason
|
||||
cserver.thread_min_client_threshold *= dictSize(g_pserver->cluster->nodes);
|
||||
cserver.thread_min_client_threshold = std::min(cserver.thread_min_client_threshold, 200);
|
||||
serverLog(LL_NOTICE, "Expanding min-clients-per-thread to %d due to cluster", cserver.thread_min_client_threshold);
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
fmterr:
|
||||
@@ -626,18 +617,6 @@ clusterLink *createClusterLink(clusterNode *node) {
|
||||
* This function will just make sure that the original node associated
|
||||
* with this link will have the 'link' field set to NULL. */
|
||||
void freeClusterLink(clusterLink *link) {
|
||||
if (ielFromEventLoop(serverTL->el) != IDX_EVENT_LOOP_MAIN)
|
||||
{
|
||||
// we can't perform this operation on this thread, queue it on the main thread
|
||||
if (link->node)
|
||||
link->node->link = NULL;
|
||||
link->node = nullptr;
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [link]{
|
||||
freeClusterLink(link);
|
||||
});
|
||||
serverAssert(res == AE_OK);
|
||||
return;
|
||||
}
|
||||
if (link->fd != -1) {
|
||||
aeDeleteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, link->fd, AE_READABLE|AE_WRITABLE);
|
||||
}
|
||||
@@ -2160,35 +2139,21 @@ void handleLinkIOError(clusterLink *link) {
|
||||
* consumed by write(). We don't try to optimize this for speed too much
|
||||
* as this is a very low traffic channel. */
|
||||
void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
serverAssert(ielFromEventLoop(el) == IDX_EVENT_LOOP_MAIN);
|
||||
clusterLink *link = (clusterLink*) privdata;
|
||||
ssize_t nwritten;
|
||||
UNUSED(el);
|
||||
UNUSED(mask);
|
||||
|
||||
// We're about to release the lock, so the link's sndbuf needs to be owned fully by us
|
||||
// allocate a new one in case anyone tries to write while we're waiting
|
||||
sds sndbuf = link->sndbuf;
|
||||
link->sndbuf = sdsempty();
|
||||
|
||||
aeReleaseLock();
|
||||
nwritten = write(fd, sndbuf, sdslen(sndbuf));
|
||||
aeAcquireLock();
|
||||
|
||||
nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf));
|
||||
if (nwritten <= 0) {
|
||||
serverLog(LL_DEBUG,"I/O error writing to node link: %s",
|
||||
(nwritten == -1) ? strerror(errno) : "short write");
|
||||
sdsfree(sndbuf);
|
||||
handleLinkIOError(link);
|
||||
return;
|
||||
}
|
||||
sdsrange(sndbuf,nwritten,-1);
|
||||
// Restore our send buffer, ensuring any unsent data is first
|
||||
sndbuf = sdscat(sndbuf, link->sndbuf);
|
||||
sdsfree(link->sndbuf);
|
||||
link->sndbuf = sndbuf;
|
||||
sdsrange(link->sndbuf,nwritten,-1);
|
||||
if (sdslen(link->sndbuf) == 0)
|
||||
aeDeleteFileEvent(el, link->fd, AE_WRITABLE);
|
||||
aeDeleteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, link->fd, AE_WRITABLE);
|
||||
}
|
||||
|
||||
/* Read data. Try to read the first field of the header first to check the
|
||||
@@ -2263,10 +2228,9 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* the link to be invalidated, so it is safe to call this function
|
||||
* from event handlers that will do stuff with the same link later. */
|
||||
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
if (sdslen(link->sndbuf) == 0 && msglen != 0)
|
||||
aeCreateRemoteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,link->fd,AE_WRITABLE|AE_BARRIER,
|
||||
clusterWriteHandler,link,false);
|
||||
aeCreateFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,link->fd,AE_WRITABLE|AE_BARRIER,
|
||||
clusterWriteHandler,link);
|
||||
|
||||
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
|
||||
|
||||
@@ -3320,7 +3284,7 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
void resetManualFailover(void) {
|
||||
if (g_pserver->cluster->mf_end && clientsArePaused()) {
|
||||
g_pserver->clients_pause_end_time = 0;
|
||||
unpauseClientsIfNecessary();
|
||||
clientsArePaused(); /* Just use the side effect of the function. */
|
||||
}
|
||||
g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */
|
||||
g_pserver->cluster->mf_can_start = 0;
|
||||
@@ -4190,76 +4154,57 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
dictIterator *di = dictGetSafeIterator(g_pserver->cluster->nodes);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = (clusterNode*)dictGetVal(de);
|
||||
int start = -1;
|
||||
int j = 0, start = -1;
|
||||
|
||||
/* Skip slaves (that are iterated when producing the output of their
|
||||
* master) and masters not serving any slot. */
|
||||
if (!nodeIsMaster(node) || node->numslots == 0) continue;
|
||||
|
||||
static_assert((CLUSTER_SLOTS % (sizeof(uint32_t)*8)) == 0, "code below assumes the bitfield is a multiple of sizeof(unsinged)");
|
||||
|
||||
for (unsigned iw = 0; iw < (CLUSTER_SLOTS/sizeof(uint32_t)/8); ++iw)
|
||||
{
|
||||
uint32_t wordCur = reinterpret_cast<uint32_t*>(node->slots)[iw];
|
||||
if (iw != ((CLUSTER_SLOTS/sizeof(uint32_t)/8)-1))
|
||||
{
|
||||
if (start == -1 && wordCur == 0)
|
||||
continue;
|
||||
if (start != -1 && (wordCur+1)==0)
|
||||
continue;
|
||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||
int bit, i;
|
||||
|
||||
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {
|
||||
if (start == -1) start = j;
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
int nested_elements = 3; /* slots (2) + master addr (1). */
|
||||
void *nested_replylen = addReplyDeferredLen(c);
|
||||
|
||||
unsigned ibitStartLoop = iw*sizeof(uint32_t)*8;
|
||||
|
||||
for (unsigned j = ibitStartLoop; j < (iw+1)*sizeof(uint32_t)*8; j++) {
|
||||
int i;
|
||||
int bit = (int)(wordCur & 1);
|
||||
wordCur >>= 1;
|
||||
if (bit != 0) {
|
||||
if (start == -1) start = j;
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
/* If slot exists in output map, add to it's list.
|
||||
* else, create a new output map for this slot */
|
||||
if (start == j-1) {
|
||||
addReplyLongLong(c, start); /* only one slot; low==high */
|
||||
addReplyLongLong(c, start);
|
||||
} else {
|
||||
addReplyLongLong(c, start); /* low */
|
||||
addReplyLongLong(c, j-1); /* high */
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
int nested_elements = 3; /* slots (2) + master addr (1). */
|
||||
void *nested_replylen = addReplyDeferredLen(c);
|
||||
start = -1;
|
||||
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
/* First node reply position is always the master */
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
|
||||
/* If slot exists in output map, add to it's list.
|
||||
* else, create a new output map for this slot */
|
||||
if (start == j-1) {
|
||||
addReplyLongLong(c, start); /* only one slot; low==high */
|
||||
addReplyLongLong(c, start);
|
||||
} else {
|
||||
addReplyLongLong(c, start); /* low */
|
||||
addReplyLongLong(c, j-1); /* high */
|
||||
}
|
||||
start = -1;
|
||||
|
||||
/* First node reply position is always the master */
|
||||
/* Remaining nodes in reply are replicas for slot range */
|
||||
for (i = 0; i < node->numslaves; i++) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
|
||||
/* Remaining nodes in reply are replicas for slot range */
|
||||
for (i = 0; i < node->numslaves; i++) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
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++;
|
||||
}
|
||||
setDeferredArrayLen(c, nested_replylen, nested_elements);
|
||||
num_masters++;
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredArrayLen(c, nested_replylen, nested_elements);
|
||||
num_masters++;
|
||||
}
|
||||
}
|
||||
serverAssert(start == -1);
|
||||
}
|
||||
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c, slot_replylen, num_masters);
|
||||
}
|
||||
|
||||
+3
-18
@@ -405,7 +405,7 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if ((!strcasecmp(argv[0],"slaveof") ||
|
||||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
|
||||
slaveof_linenum = linenum;
|
||||
replicationAddMaster(argv[1], atoi(argv[2]));
|
||||
replicationAddMaster(sdsnew(argv[1]), atoi(argv[2]));
|
||||
} else if ((!strcasecmp(argv[0],"repl-ping-slave-period") ||
|
||||
!strcasecmp(argv[0],"repl-ping-replica-period")) &&
|
||||
argc == 2)
|
||||
@@ -802,14 +802,6 @@ void loadServerConfigFromString(char *config) {
|
||||
g_fTestMode = yesnotoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"rdbfuzz-mode")) {
|
||||
// NOP, handled in main
|
||||
} else if (!strcasecmp(argv[0],"enable-pro")) {
|
||||
cserver.fUsePro = true;
|
||||
break;
|
||||
} else if (!strcasecmp(argv[0],"min-clients-per-thread") && argc == 2) {
|
||||
cserver.thread_min_client_threshold = atoi(argv[1]);
|
||||
if (cserver.thread_min_client_threshold < 0 || cserver.thread_min_client_threshold > 400) {
|
||||
err = "min-thread-client must be between 0 and 400"; goto loaderr;
|
||||
}
|
||||
} else {
|
||||
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
||||
}
|
||||
@@ -1496,15 +1488,8 @@ void configGetCommand(client *c) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(g_pserver->masters, &li);
|
||||
bool fFirst = true;
|
||||
while ((ln = listNext(&li)))
|
||||
{
|
||||
if (!fFirst)
|
||||
{
|
||||
addReplyBulkCString(c,optname);
|
||||
matches++;
|
||||
}
|
||||
fFirst = false;
|
||||
struct redisMaster *mi = (struct redisMaster*)listNodeValue(ln);
|
||||
snprintf(buf,sizeof(buf),"%s %d",
|
||||
mi->masterhost, mi->masterport);
|
||||
@@ -1742,7 +1727,7 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *opti
|
||||
}
|
||||
|
||||
/* Write the long long 'bytes' value as a string in a way that is parsable
|
||||
* inside keydb.conf. If possible uses the GB, MB, KB notation. */
|
||||
* inside redis.conf. If possible uses the GB, MB, KB notation. */
|
||||
int rewriteConfigFormatMemory(char *buf, size_t len, long long bytes) {
|
||||
int gb = 1024*1024*1024;
|
||||
int mb = 1024*1024;
|
||||
@@ -1905,7 +1890,7 @@ void rewriteConfigDirOption(struct rewriteConfigState *state) {
|
||||
void rewriteConfigSlaveofOption(struct rewriteConfigState *state, const char *option) {
|
||||
/* If this is a master, we want all the slaveof config options
|
||||
* in the file to be removed. Note that if this is a cluster instance
|
||||
* we don't want a slaveof directive inside keydb.conf. */
|
||||
* we don't want a slaveof directive inside redis.conf. */
|
||||
if (g_pserver->cluster_enabled || listLength(g_pserver->masters) == 0) {
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
return;
|
||||
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
#include "server.h"
|
||||
#include "cron.h"
|
||||
|
||||
void freeCronObject(robj_roptr o)
|
||||
{
|
||||
delete reinterpret_cast<const cronjob*>(ptrFromObj(o));
|
||||
}
|
||||
|
||||
// CRON [name] [single shot] [optional: start] [delay] [script] [numkeys] [key N] [arg N]
|
||||
void cronCommand(client *c)
|
||||
{
|
||||
int arg_offset = 0;
|
||||
static const int ARG_NAME = 1;
|
||||
static const int ARG_SINGLESHOT = 2;
|
||||
static const int ARG_EXPIRE = 3;
|
||||
#define ARG_SCRIPT (4+arg_offset)
|
||||
#define ARG_NUMKEYS (5+arg_offset)
|
||||
#define ARG_KEYSTART (6+arg_offset)
|
||||
|
||||
bool fSingleShot = false;
|
||||
if (strcasecmp("single", szFromObj(c->argv[ARG_SINGLESHOT])) == 0) {
|
||||
fSingleShot = true;
|
||||
} else {
|
||||
if (strcasecmp("repeat", szFromObj(c->argv[ARG_SINGLESHOT])) != 0) {
|
||||
addReply(c, shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
long long interval;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[ARG_EXPIRE], &interval, "missing expire time") != C_OK)
|
||||
return;
|
||||
|
||||
long long base = g_pserver->mstime;
|
||||
if (getLongLongFromObject(c->argv[ARG_EXPIRE+1], &base) == C_OK) {
|
||||
arg_offset++;
|
||||
std::swap(base, interval);
|
||||
}
|
||||
|
||||
if (interval <= 0)
|
||||
{
|
||||
addReplyError(c, "interval must be positive");
|
||||
return;
|
||||
}
|
||||
|
||||
long numkeys = 0;
|
||||
if (c->argc > ARG_NUMKEYS)
|
||||
{
|
||||
if (getLongFromObjectOrReply(c, c->argv[ARG_NUMKEYS], &numkeys, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc < (6 + numkeys)) {
|
||||
addReplyError(c, "Missing arguments or numkeys is too big");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<cronjob> spjob = std::make_unique<cronjob>();
|
||||
spjob->script = sdsstring(sdsdup(szFromObj(c->argv[ARG_SCRIPT])));
|
||||
spjob->interval = (uint64_t)interval;
|
||||
spjob->startTime = (uint64_t)base;
|
||||
spjob->fSingleShot = fSingleShot;
|
||||
spjob->dbNum = c->db - g_pserver->db;
|
||||
for (long i = 0; i < numkeys; ++i)
|
||||
spjob->veckeys.emplace_back(sdsdup(szFromObj(c->argv[ARG_KEYSTART+i])));
|
||||
for (long i = ARG_KEYSTART + numkeys; i < c->argc; ++i)
|
||||
spjob->vecargs.emplace_back(sdsdup(szFromObj(c->argv[i])));
|
||||
|
||||
robj *o = createObject(OBJ_CRON, spjob.release());
|
||||
setKey(c->db, c->argv[ARG_NAME], o);
|
||||
decrRefCount(o);
|
||||
// use an expire to trigger execution. Note: We use a subkey expire here so legacy clients don't delete it.
|
||||
setExpire(c, c->db, c->argv[ARG_NAME], c->argv[ARG_NAME], base + interval);
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
|
||||
void executeCronJobExpireHook(const char *key, robj *o)
|
||||
{
|
||||
serverAssert(o->type == OBJ_CRON);
|
||||
cronjob *job = (cronjob*)ptrFromObj(o);
|
||||
|
||||
client *cFake = createClient(-1, IDX_EVENT_LOOP_MAIN);
|
||||
cFake->lock.lock();
|
||||
cFake->authenticated = 1;
|
||||
cFake->puser = nullptr;
|
||||
selectDb(cFake, job->dbNum);
|
||||
serverAssert(cFake->argc == 0);
|
||||
|
||||
// Setup the args for the EVAL command
|
||||
cFake->argc = 3 + job->veckeys.size() + job->vecargs.size();
|
||||
cFake->argv = (robj**)zmalloc(sizeof(robj*) * cFake->argc, MALLOC_LOCAL);
|
||||
cFake->argv[0] = createStringObject("EVAL", 4);
|
||||
cFake->argv[1] = createStringObject(job->script.get(), job->script.size());
|
||||
cFake->argv[2] = createStringObjectFromLongLong(job->veckeys.size());
|
||||
for (size_t i = 0; i < job->veckeys.size(); ++i)
|
||||
cFake->argv[3+i] = createStringObject(job->veckeys[i].get(), job->veckeys[i].size());
|
||||
for (size_t i = 0; i < job->vecargs.size(); ++i)
|
||||
cFake->argv[3+job->veckeys.size()+i] = createStringObject(job->vecargs[i].get(), job->vecargs[i].size());
|
||||
|
||||
evalCommand(cFake);
|
||||
resetClient(cFake);
|
||||
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
int dbId = job->dbNum;
|
||||
if (job->fSingleShot)
|
||||
{
|
||||
dbSyncDelete(cFake->db, keyobj);
|
||||
}
|
||||
else
|
||||
{
|
||||
job->startTime += job->interval;
|
||||
if (job->startTime < (uint64_t)g_pserver->mstime)
|
||||
{
|
||||
// If we are more than one interval in the past then fast forward to
|
||||
// the first interval still in the future. If startTime wasn't zero align
|
||||
// this to the original startTime, if it was zero align to now
|
||||
if (job->startTime == job->interval)
|
||||
{ // startTime was 0
|
||||
job->startTime = g_pserver->mstime + job->interval;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delta = g_pserver->mstime - job->startTime;
|
||||
auto multiple = (delta / job->interval)+1;
|
||||
job->startTime += job->interval * multiple;
|
||||
}
|
||||
}
|
||||
setExpire(cFake, cFake->db, keyobj, keyobj, job->startTime);
|
||||
}
|
||||
|
||||
notifyKeyspaceEvent(NOTIFY_KEYEVENT, "CRON Executed", keyobj, dbId);
|
||||
decrRefCount(keyobj);
|
||||
|
||||
// o is invalid at this point
|
||||
freeClient(cFake);
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
struct cronjob
|
||||
{
|
||||
sdsstring script;
|
||||
uint64_t interval;
|
||||
uint64_t startTime;
|
||||
std::vector<sdsstring> veckeys;
|
||||
std::vector<sdsstring> vecargs;
|
||||
int dbNum = 0;
|
||||
bool fSingleShot = false;
|
||||
};
|
||||
|
||||
void freeCronObject(robj_roptr o);
|
||||
void executeCronJobExpireHook(const char *key, robj *o);
|
||||
void cronCommand(client *c);
|
||||
+17
-14
@@ -177,8 +177,9 @@ robj_roptr lookupKeyRead(redisDb *db, robj *key) {
|
||||
* Returns the linked value object if the key exists or NULL if the key
|
||||
* does not exist in the specified DB. */
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
expireIfNeeded(db,key);
|
||||
robj *o = lookupKey(db,key,LOOKUP_UPDATEMVCC);
|
||||
if (expireIfNeeded(db,key))
|
||||
o = NULL;
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -642,6 +643,10 @@ void keysCommand(client *c) {
|
||||
unsigned long numkeys = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
|
||||
#ifdef MULTITHREADED_KEYS
|
||||
aeReleaseLock();
|
||||
#endif
|
||||
|
||||
di = dictGetSafeIterator(c->db->pdict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
@@ -659,6 +664,14 @@ void keysCommand(client *c) {
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c,replylen,numkeys);
|
||||
|
||||
#ifdef MULTITHREADED_KEYS
|
||||
fastlock_unlock(&c->db->lock); // we must release the DB lock before acquiring the AE lock to prevent deadlocks
|
||||
AeLocker lock;
|
||||
lock.arm(c);
|
||||
fastlock_lock(&c->db->lock); // we still need the DB lock
|
||||
lock.release();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* This callback is used by scanGenericCommand in order to collect elements
|
||||
@@ -977,7 +990,7 @@ void shutdownCommand(client *c) {
|
||||
* Also when in Sentinel mode clear the SAVE flag and force NOSAVE. */
|
||||
if (g_pserver->loading || g_pserver->sentinel_mode)
|
||||
flags = (flags & ~SHUTDOWN_SAVE) | SHUTDOWN_NOSAVE;
|
||||
if (prepareForShutdown(flags) == C_OK) throw ShutdownException();
|
||||
if (prepareForShutdown(flags) == C_OK) exit(0);
|
||||
addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
|
||||
}
|
||||
|
||||
@@ -1305,14 +1318,6 @@ void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when)
|
||||
rememberSlaveKeyWithExpire(db,key);
|
||||
}
|
||||
|
||||
redisDb::~redisDb()
|
||||
{
|
||||
dictRelease(watched_keys);
|
||||
dictRelease(ready_keys);
|
||||
dictRelease(blocking_keys);
|
||||
listRelease(defrag_later);
|
||||
}
|
||||
|
||||
void setExpire(client *c, redisDb *db, robj *key, expireEntry &&e)
|
||||
{
|
||||
dictEntry *kde;
|
||||
@@ -1381,9 +1386,7 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
|
||||
if (g_pserver->aof_state != AOF_OFF)
|
||||
feedAppendOnlyFile(cserver.delCommand,db->id,argv,2);
|
||||
// Active replicas do their own expiries, do not propogate
|
||||
if (!g_pserver->fActiveReplica)
|
||||
replicationFeedSlaves(g_pserver->slaves,db->id,argv,2);
|
||||
replicationFeedSlaves(g_pserver->slaves,db->id,argv,2);
|
||||
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
@@ -1451,7 +1454,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
* 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 (listLength(g_pserver->masters) && !g_pserver->fActiveReplica) return 1;
|
||||
if (listLength(g_pserver->masters)) return 1;
|
||||
|
||||
/* Delete the key */
|
||||
g_pserver->stat_expiredkeys++;
|
||||
|
||||
+2
-20
@@ -55,7 +55,7 @@ typedef ucontext_t sigcontext_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int g_fInCrash = false;
|
||||
bool g_fInCrash = false;
|
||||
|
||||
/* ================================= Debugging ============================== */
|
||||
|
||||
@@ -685,26 +685,8 @@ NULL
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2) {
|
||||
stringmatchlen_fuzz_test();
|
||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]), "force-master") && c->argc == 3) {
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]), "force-master") && c->argc == 2) {
|
||||
c->flags |= CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY;
|
||||
if (!strcasecmp(szFromObj(c->argv[2]), "yes"))
|
||||
{
|
||||
redisMaster *mi = (redisMaster*)zcalloc(sizeof(redisMaster), MALLOC_LOCAL);
|
||||
mi->master = c;
|
||||
listAddNodeHead(g_pserver->masters, mi);
|
||||
}
|
||||
else if (strcasecmp(szFromObj(c->argv[2]), "flagonly")) // if we didn't set flagonly assume its an unset
|
||||
{
|
||||
serverAssert(c->flags & CLIENT_MASTER);
|
||||
if (listLength(g_pserver->masters))
|
||||
{
|
||||
redisMaster *mi = (redisMaster*)listNodeValue(listFirst(g_pserver->masters));
|
||||
serverAssert(mi->master == c);
|
||||
listDelNode(g_pserver->masters, listFirst(g_pserver->masters));
|
||||
zfree(mi);
|
||||
}
|
||||
c->flags &= ~(CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY);
|
||||
}
|
||||
addReply(c, shared.ok);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
|
||||
+5
-15
@@ -48,7 +48,7 @@ extern "C" int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
|
||||
/* forward declarations*/
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
|
||||
bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey);
|
||||
void replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
@@ -407,7 +407,7 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
|
||||
void replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
|
||||
auto itr = set.find(oldkey);
|
||||
if (itr != set.end())
|
||||
{
|
||||
@@ -415,10 +415,7 @@ bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
|
||||
eNew.setKeyUnsafe(newkey);
|
||||
set.erase(itr);
|
||||
set.insert(eNew);
|
||||
serverAssert(set.find(newkey) != set.end());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
@@ -780,22 +777,16 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
long defragged = 0;
|
||||
sds newsds;
|
||||
|
||||
ob = (robj*)dictGetVal(de);
|
||||
|
||||
/* Try to defrag the key name. */
|
||||
newsds = activeDefragSds(keysds);
|
||||
if (newsds)
|
||||
{
|
||||
defragged++, de->key = newsds;
|
||||
if (!db->setexpire->empty()) {
|
||||
bool fReplaced = replaceSateliteOSetKeyPtr(*db->setexpire, keysds, newsds);
|
||||
serverAssert(fReplaced == ob->FExpires());
|
||||
} else {
|
||||
serverAssert(!ob->FExpires());
|
||||
}
|
||||
if (!db->setexpire->empty()) {
|
||||
replaceSateliteOSetKeyPtr(*db->setexpire, keysds, newsds);
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
ob = (robj*)dictGetVal(de);
|
||||
if ((newob = activeDefragStringOb(ob, &defragged))) {
|
||||
de->v.val = newob;
|
||||
ob = newob;
|
||||
@@ -848,7 +839,6 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
|
||||
return defragged;
|
||||
}
|
||||
|
||||
|
||||
+3
-42
@@ -31,7 +31,6 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "cron.h"
|
||||
|
||||
void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
@@ -101,32 +100,9 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_HASH:
|
||||
if (hashTypeDelete(val,(sds)pfat->nextExpireEntry().spsubkey.get())) {
|
||||
deleted++;
|
||||
if (hashTypeLength(val) == 0) {
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_ZSET:
|
||||
if (zsetDel(val,(sds)pfat->nextExpireEntry().spsubkey.get())) {
|
||||
deleted++;
|
||||
if (zsetLength(val) == 0) {
|
||||
activeExpireCycleExpireFullKey(db, e.key());
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_CRON:
|
||||
executeCronJobExpireHook(e.key(), val);
|
||||
return;
|
||||
|
||||
case OBJ_LIST:
|
||||
case OBJ_ZSET:
|
||||
case OBJ_HASH:
|
||||
default:
|
||||
serverAssert(false);
|
||||
}
|
||||
@@ -185,7 +161,6 @@ void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, lo
|
||||
return;
|
||||
}
|
||||
|
||||
double dblT;
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
@@ -195,20 +170,6 @@ void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, lo
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_HASH:
|
||||
if (!hashTypeExists(val, szFromObj(subkey))) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case OBJ_ZSET:
|
||||
if (zsetScore(val, szFromObj(subkey), &dblT) == C_ERR) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
addReplyError(c, "object type is unsupported");
|
||||
return;
|
||||
@@ -574,7 +535,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
*
|
||||
* Instead we take the other branch of the IF statement setting an expire
|
||||
* (possibly in the past) and wait for an explicit DEL from the master. */
|
||||
if (when <= mstime() && !g_pserver->loading && (!listLength(g_pserver->masters) || g_pserver->fActiveReplica)) {
|
||||
if (when <= mstime() && !g_pserver->loading && !listLength(g_pserver->masters)) {
|
||||
robj *aux;
|
||||
|
||||
int deleted = g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
|
||||
|
||||
+42
-134
@@ -36,13 +36,10 @@
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <limits.h>
|
||||
#include <map>
|
||||
#ifdef __linux__
|
||||
#include <linux/futex.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
@@ -60,7 +57,6 @@
|
||||
#define UNUSED(x) ((void)x)
|
||||
#endif
|
||||
|
||||
extern int g_fInCrash;
|
||||
|
||||
/****************************************************
|
||||
*
|
||||
@@ -74,10 +70,6 @@ extern int g_fInCrash;
|
||||
#define __has_feature(x) 0
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
extern "C" void unlock_futex(struct fastlock *lock, uint16_t ifutex);
|
||||
#endif
|
||||
|
||||
#if __has_feature(thread_sanitizer)
|
||||
|
||||
/* Report that a lock has been created at address "lock". */
|
||||
@@ -133,88 +125,9 @@ extern "C" void unlock_futex(struct fastlock *lock, uint16_t ifutex);
|
||||
|
||||
#endif
|
||||
|
||||
#pragma weak _serverPanic
|
||||
extern "C" __attribute__((weak)) void _serverPanic(const char * /*file*/, int /*line*/, const char * /*msg*/, ...)
|
||||
{
|
||||
*((char*)-1) = 'x';
|
||||
}
|
||||
|
||||
#pragma weak serverLog
|
||||
__attribute__((weak)) void serverLog(int , const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vprintf(fmt, args);
|
||||
va_end(args);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
class DeadlockDetector
|
||||
{
|
||||
std::map<pid_t, fastlock *> m_mapwait;
|
||||
fastlock m_lock { "deadlock detector" };
|
||||
public:
|
||||
void registerwait(fastlock *lock, pid_t thispid)
|
||||
{
|
||||
if (lock == &m_lock || g_fInCrash)
|
||||
return;
|
||||
fastlock_lock(&m_lock);
|
||||
m_mapwait.insert(std::make_pair(thispid, lock));
|
||||
|
||||
// Detect cycles
|
||||
pid_t pidCheck = thispid;
|
||||
size_t cchecks = 0;
|
||||
for (;;)
|
||||
{
|
||||
auto itr = m_mapwait.find(pidCheck);
|
||||
if (itr == m_mapwait.end())
|
||||
break;
|
||||
pidCheck = itr->second->m_pidOwner;
|
||||
if (pidCheck == thispid)
|
||||
{
|
||||
// Deadlock detected, printout some debugging info and crash
|
||||
serverLog(3 /*LL_WARNING*/, "\n\n");
|
||||
serverLog(3 /*LL_WARNING*/, "!!! ERROR: Deadlock detected !!!");
|
||||
pidCheck = thispid;
|
||||
for (;;)
|
||||
{
|
||||
auto itr = m_mapwait.find(pidCheck);
|
||||
serverLog(3 /* LL_WARNING */, "\t%d: (%p) %s", pidCheck, itr->second, itr->second->szName);
|
||||
pidCheck = itr->second->m_pidOwner;
|
||||
if (pidCheck == thispid)
|
||||
break;
|
||||
}
|
||||
serverLog(3 /*LL_WARNING*/, "!!! KeyDB Will Now Crash !!!");
|
||||
_serverPanic(__FILE__, __LINE__, "Deadlock detected");
|
||||
}
|
||||
|
||||
if (cchecks > m_mapwait.size())
|
||||
break; // There is a cycle but we're not in it
|
||||
++cchecks;
|
||||
}
|
||||
fastlock_unlock(&m_lock);
|
||||
}
|
||||
|
||||
void clearwait(fastlock *lock, pid_t thispid)
|
||||
{
|
||||
if (lock == &m_lock || g_fInCrash)
|
||||
return;
|
||||
fastlock_lock(&m_lock);
|
||||
m_mapwait.erase(thispid);
|
||||
fastlock_unlock(&m_lock);
|
||||
}
|
||||
};
|
||||
|
||||
DeadlockDetector g_dlock;
|
||||
|
||||
static_assert(sizeof(pid_t) <= sizeof(fastlock::m_pidOwner), "fastlock::m_pidOwner not large enough");
|
||||
uint64_t g_longwaits = 0;
|
||||
|
||||
extern "C" void fastlock_panic(struct fastlock *lock)
|
||||
{
|
||||
_serverPanic(__FILE__, __LINE__, "fastlock lock/unlock mismatch for: %s", lock->szName);
|
||||
}
|
||||
|
||||
uint64_t fastlock_getlongwaitcount()
|
||||
{
|
||||
uint64_t rval;
|
||||
@@ -222,6 +135,7 @@ uint64_t fastlock_getlongwaitcount()
|
||||
return rval;
|
||||
}
|
||||
|
||||
#ifndef ASM_SPINLOCK
|
||||
#ifdef __linux__
|
||||
static int futex(volatile unsigned *uaddr, int futex_op, int val,
|
||||
const struct timespec *timeout, int val3)
|
||||
@@ -230,6 +144,7 @@ static int futex(volatile unsigned *uaddr, int futex_op, int val,
|
||||
timeout, uaddr, val3);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern "C" pid_t gettid()
|
||||
{
|
||||
@@ -248,29 +163,13 @@ extern "C" pid_t gettid()
|
||||
return pidCache;
|
||||
}
|
||||
|
||||
extern "C" void fastlock_sleep(fastlock *lock, pid_t pid, unsigned wake, unsigned mask)
|
||||
{
|
||||
#ifdef __linux__
|
||||
g_dlock.registerwait(lock, pid);
|
||||
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
|
||||
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, wake, nullptr, mask);
|
||||
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
|
||||
g_dlock.clearwait(lock, pid);
|
||||
#endif
|
||||
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
extern "C" void fastlock_init(struct fastlock *lock, const char *name)
|
||||
extern "C" void fastlock_init(struct fastlock *lock)
|
||||
{
|
||||
lock->m_ticket.m_active = 0;
|
||||
lock->m_ticket.m_avail = 0;
|
||||
lock->m_depth = 0;
|
||||
lock->m_pidOwner = -1;
|
||||
lock->futex = 0;
|
||||
int cch = strlen(name);
|
||||
cch = std::min<int>(cch, sizeof(lock->szName)-1);
|
||||
memcpy(lock->szName, name, cch);
|
||||
lock->szName[cch] = '\0';
|
||||
ANNOTATE_RWLOCK_CREATE(lock);
|
||||
}
|
||||
|
||||
@@ -285,12 +184,12 @@ extern "C" void fastlock_lock(struct fastlock *lock)
|
||||
return;
|
||||
}
|
||||
|
||||
int tid = gettid();
|
||||
unsigned myticket = __atomic_fetch_add(&lock->m_ticket.m_avail, 1, __ATOMIC_RELEASE);
|
||||
#ifdef __linux__
|
||||
unsigned mask = (1U << (myticket % 32));
|
||||
unsigned cloops = 0;
|
||||
#endif
|
||||
int cloops = 0;
|
||||
ticket ticketT;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
__atomic_load(&lock->m_ticket.u, &ticketT.u, __ATOMIC_ACQUIRE);
|
||||
@@ -298,17 +197,21 @@ extern "C" void fastlock_lock(struct fastlock *lock)
|
||||
break;
|
||||
|
||||
#if defined(__i386__) || defined(__amd64__)
|
||||
__asm__ __volatile__ ("pause");
|
||||
#elif defined(__aarch64__)
|
||||
__asm__ __volatile__ ("yield");
|
||||
__asm__ ("pause");
|
||||
#endif
|
||||
if ((++cloops % 0x100000) == 0)
|
||||
if ((++cloops % 1024*1024) == 0)
|
||||
{
|
||||
fastlock_sleep(lock, tid, ticketT.u, mask);
|
||||
#ifdef __linux__
|
||||
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
|
||||
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, ticketT.u, nullptr, mask);
|
||||
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
|
||||
#endif
|
||||
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
}
|
||||
|
||||
lock->m_depth = 1;
|
||||
int tid = gettid();
|
||||
__atomic_store(&lock->m_pidOwner, &tid, __ATOMIC_RELEASE);
|
||||
ANNOTATE_RWLOCK_ACQUIRED(lock, true);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
@@ -335,7 +238,7 @@ extern "C" int fastlock_trylock(struct fastlock *lock, int fWeak)
|
||||
|
||||
struct ticket ticket_expect { { { active, active } } };
|
||||
struct ticket ticket_setiflocked { { { active, next } } };
|
||||
if (__atomic_compare_exchange(&lock->m_ticket.u, &ticket_expect.u, &ticket_setiflocked.u, fWeak /*weak*/, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
||||
if (__atomic_compare_exchange(&lock->m_ticket, &ticket_expect, &ticket_setiflocked, fWeak /*weak*/, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
|
||||
{
|
||||
lock->m_depth = 1;
|
||||
tid = gettid();
|
||||
@@ -346,6 +249,31 @@ extern "C" int fastlock_trylock(struct fastlock *lock, int fWeak)
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#define ROL32(v, shift) ((v << shift) | (v >> (32-shift)))
|
||||
void unlock_futex(struct fastlock *lock, uint16_t ifutex)
|
||||
{
|
||||
unsigned mask = (1U << (ifutex % 32));
|
||||
unsigned futexT;
|
||||
__atomic_load(&lock->futex, &futexT, __ATOMIC_RELAXED);
|
||||
futexT &= mask;
|
||||
|
||||
if (futexT == 0)
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
__atomic_load(&lock->futex, &futexT, __ATOMIC_ACQUIRE);
|
||||
futexT &= mask;
|
||||
if (!futexT)
|
||||
break;
|
||||
|
||||
if (futex(&lock->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask) == 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" void fastlock_unlock(struct fastlock *lock)
|
||||
{
|
||||
--lock->m_depth;
|
||||
@@ -368,26 +296,6 @@ extern "C" void fastlock_unlock(struct fastlock *lock)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#define ROL32(v, shift) ((v << shift) | (v >> (32-shift)))
|
||||
extern "C" void unlock_futex(struct fastlock *lock, uint16_t ifutex)
|
||||
{
|
||||
unsigned mask = (1U << (ifutex % 32));
|
||||
unsigned futexT;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
__atomic_load(&lock->futex, &futexT, __ATOMIC_ACQUIRE);
|
||||
futexT &= mask;
|
||||
if (!futexT)
|
||||
break;
|
||||
|
||||
if (futex(&lock->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask) == 1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" void fastlock_free(struct fastlock *lock)
|
||||
{
|
||||
// NOP
|
||||
@@ -417,4 +325,4 @@ void fastlock_lock_recursive(struct fastlock *lock, int nesting)
|
||||
{
|
||||
fastlock_lock(lock);
|
||||
lock->m_depth = nesting;
|
||||
}
|
||||
}
|
||||
+8
-13
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -8,7 +7,7 @@ extern "C" {
|
||||
|
||||
/* Begin C API */
|
||||
struct fastlock;
|
||||
void fastlock_init(struct fastlock *lock, const char *name);
|
||||
void fastlock_init(struct fastlock *lock);
|
||||
void fastlock_lock(struct fastlock *lock);
|
||||
int fastlock_trylock(struct fastlock *lock, int fWeak);
|
||||
void fastlock_unlock(struct fastlock *lock);
|
||||
@@ -41,31 +40,29 @@ struct ticket
|
||||
|
||||
struct fastlock
|
||||
{
|
||||
volatile struct ticket m_ticket;
|
||||
|
||||
volatile int m_pidOwner;
|
||||
volatile int m_depth;
|
||||
char szName[56];
|
||||
/* Volatile data on seperate cache line */
|
||||
volatile struct ticket m_ticket;
|
||||
unsigned futex;
|
||||
char padding[56]; // ensure ticket and futex are on their own independent cache line
|
||||
|
||||
#ifdef __cplusplus
|
||||
fastlock(const char *name)
|
||||
fastlock()
|
||||
{
|
||||
fastlock_init(this, name);
|
||||
fastlock_init(this);
|
||||
}
|
||||
|
||||
inline void lock()
|
||||
void lock()
|
||||
{
|
||||
fastlock_lock(this);
|
||||
}
|
||||
|
||||
inline bool try_lock(bool fWeak = false)
|
||||
bool try_lock(bool fWeak = false)
|
||||
{
|
||||
return !!fastlock_trylock(this, fWeak);
|
||||
}
|
||||
|
||||
inline void unlock()
|
||||
void unlock()
|
||||
{
|
||||
fastlock_unlock(this);
|
||||
}
|
||||
@@ -83,5 +80,3 @@ struct fastlock
|
||||
bool fOwnLock(); // true if this thread owns the lock, NOTE: not 100% reliable, use for debugging only
|
||||
#endif
|
||||
};
|
||||
|
||||
static_assert(offsetof(struct fastlock, m_ticket) == 64, "ensure padding is correct");
|
||||
+125
-120
@@ -1,152 +1,157 @@
|
||||
.intel_syntax noprefix
|
||||
.text
|
||||
section .text
|
||||
|
||||
.extern gettid
|
||||
.extern fastlock_sleep
|
||||
extern gettid
|
||||
extern sched_yield
|
||||
extern g_longwaits
|
||||
|
||||
# This is the first use of assembly in this codebase, a valid question is WHY?
|
||||
# The spinlock we implement here is performance critical, and simply put GCC
|
||||
# emits awful code. The original C code is left in fastlock.cpp for reference
|
||||
# and x-plat.
|
||||
; This is the first use of assembly in this codebase, a valid question is WHY?
|
||||
; The spinlock we implement here is performance critical, and simply put GCC
|
||||
; emits awful code. The original C code is left in fastlock.cpp for reference
|
||||
; and x-plat.
|
||||
|
||||
.ALIGN 16
|
||||
.global fastlock_lock
|
||||
.type fastlock_lock,@function
|
||||
ALIGN 16
|
||||
global fastlock_lock
|
||||
fastlock_lock:
|
||||
.cfi_startproc
|
||||
.cfi_def_cfa rsp, 8
|
||||
# RDI points to the struct:
|
||||
# int32_t m_pidOwner
|
||||
# int32_t m_depth
|
||||
# [rdi+64] ...
|
||||
# uint16_t active
|
||||
# uint16_t avail
|
||||
; RDI points to the struct:
|
||||
; uint16_t active
|
||||
; uint16_t avail
|
||||
; int32_t m_pidOwner
|
||||
; int32_t m_depth
|
||||
|
||||
# First get our TID and put it in ecx
|
||||
push rdi # we need our struct pointer (also balance the stack for the call)
|
||||
.cfi_adjust_cfa_offset 8
|
||||
call gettid # get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
||||
mov esi, eax # back it up in esi
|
||||
pop rdi # get our pointer back
|
||||
.cfi_adjust_cfa_offset -8
|
||||
; First get our TID and put it in ecx
|
||||
push rdi ; we need our struct pointer (also balance the stack for the call)
|
||||
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
||||
mov esi, eax ; back it up in esi
|
||||
pop rdi ; get our pointer back
|
||||
|
||||
cmp [rdi], esi # Is the TID we got back the owner of the lock?
|
||||
je .LLocked # Don't spin in that case
|
||||
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
|
||||
je .LLocked ; Don't spin in that case
|
||||
|
||||
xor eax, eax # eliminate partial register dependency
|
||||
inc eax # we want to add one
|
||||
lock xadd [rdi+66], ax # do the xadd, ax contains the value before the addition
|
||||
# ax now contains the ticket
|
||||
# OK Start the wait loop
|
||||
xor ecx, ecx
|
||||
.ALIGN 16
|
||||
xor eax, eax ; eliminate partial register dependency
|
||||
inc eax ; we want to add one
|
||||
lock xadd [rdi+2], ax ; do the xadd, ax contains the value before the addition
|
||||
; ax now contains the ticket
|
||||
ALIGN 16
|
||||
.LLoop:
|
||||
mov edx, [rdi+64]
|
||||
cmp dx, ax # is our ticket up?
|
||||
je .LLocked # leave the loop
|
||||
mov edx, [rdi]
|
||||
cmp dx, ax ; is our ticket up?
|
||||
je .LLocked ; leave the loop
|
||||
pause
|
||||
add ecx, 0x1000 # Have we been waiting a long time? (oflow if we have)
|
||||
# 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
|
||||
jnc .LLoop # If so, give up our timeslice to someone who's doing real work
|
||||
# Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
|
||||
# But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
|
||||
# taking a long time to be released anyways. We optimize for the common case of short
|
||||
# lock intervals. That's why we're using a spinlock in the first place
|
||||
# If we get here we're going to sleep in the kernel with a futex
|
||||
push rdi
|
||||
add ecx, 1000h ; Have we been waiting a long time? (oflow if we have)
|
||||
; 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
|
||||
jnc .LLoop ; If so, give up our timeslice to someone who's doing real work
|
||||
; Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
|
||||
; But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
|
||||
; taking a long time to be released anyways. We optimize for the common case of short
|
||||
; lock intervals. That's why we're using a spinlock in the first place
|
||||
; If we get here we're going to sleep in the kernel with a futex
|
||||
push rsi
|
||||
push rax
|
||||
.cfi_adjust_cfa_offset 24
|
||||
# Setup the syscall args
|
||||
|
||||
# rdi ARG1 futex (already in rdi)
|
||||
# rsi ARG2 tid (already in esi)
|
||||
# rdx ARG3 ticketT.u (already in edx)
|
||||
bts ecx, eax # rcx ARG4 mask
|
||||
call fastlock_sleep
|
||||
# cleanup and continue
|
||||
; Setup the syscall args
|
||||
; rdi ARG1 futex (already in rdi)
|
||||
mov esi, (9 | 128) ; rsi ARG2 FUTEX_WAIT_BITSET_PRIVATE
|
||||
; rdx ARG3 ticketT.u (already in edx)
|
||||
xor r10d, r10d ; r10 ARG4 NULL
|
||||
mov r8, rdi ; r8 ARG5 dup rdi
|
||||
xor r9d, r9d
|
||||
bts r9d, eax ; r9 ARG6 mask
|
||||
mov eax, 202 ; sys_futex
|
||||
; Do the syscall
|
||||
lock or [rdi+12], r9d ; inform the unlocking thread we're waiting
|
||||
syscall ; wait for the futex
|
||||
not r9d ; convert our flag into a mask of bits not to touch
|
||||
lock and [rdi+12], r9d ; clear the flag in the futex control mask
|
||||
; cleanup and continue
|
||||
mov rcx, g_longwaits
|
||||
inc qword [rcx] ; increment our long wait counter
|
||||
pop rax
|
||||
pop rsi
|
||||
pop rdi
|
||||
.cfi_adjust_cfa_offset -24
|
||||
xor ecx, ecx # Reset our loop counter
|
||||
jmp .LLoop # Get back in the game
|
||||
.ALIGN 16
|
||||
xor ecx, ecx ; Reset our loop counter
|
||||
jmp .LLoop ; Get back in the game
|
||||
ALIGN 16
|
||||
.LLocked:
|
||||
mov [rdi], esi # lock->m_pidOwner = gettid()
|
||||
inc dword ptr [rdi+4] # lock->m_depth++
|
||||
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
|
||||
inc dword [rdi+8] ; lock->m_depth++
|
||||
ret
|
||||
.cfi_endproc
|
||||
|
||||
.ALIGN 16
|
||||
.global fastlock_trylock
|
||||
.type fastlock_trylock,@function
|
||||
ALIGN 16
|
||||
global fastlock_trylock
|
||||
fastlock_trylock:
|
||||
# RDI points to the struct:
|
||||
# int32_t m_pidOwner
|
||||
# int32_t m_depth
|
||||
# [rdi+64] ...
|
||||
# uint16_t active
|
||||
# uint16_t avail
|
||||
; RDI points to the struct:
|
||||
; uint16_t active
|
||||
; uint16_t avail
|
||||
; int32_t m_pidOwner
|
||||
; int32_t m_depth
|
||||
|
||||
# First get our TID and put it in ecx
|
||||
push rdi # we need our struct pointer (also balance the stack for the call)
|
||||
call gettid # get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
||||
mov esi, eax # back it up in esi
|
||||
pop rdi # get our pointer back
|
||||
; First get our TID and put it in ecx
|
||||
push rdi ; we need our struct pointer (also balance the stack for the call)
|
||||
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
|
||||
mov esi, eax ; back it up in esi
|
||||
pop rdi ; get our pointer back
|
||||
|
||||
cmp [rdi], esi # Is the TID we got back the owner of the lock?
|
||||
je .LRecursive # Don't spin in that case
|
||||
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
|
||||
je .LRecursive ; Don't spin in that case
|
||||
|
||||
mov eax, [rdi+64] # get both active and avail counters
|
||||
mov ecx, eax # duplicate in ecx
|
||||
ror ecx, 16 # swap upper and lower 16-bits
|
||||
cmp eax, ecx # are the upper and lower 16-bits the same?
|
||||
jnz .LAlreadyLocked # If not return failure
|
||||
mov eax, [rdi] ; get both active and avail counters
|
||||
mov ecx, eax ; duplicate in ecx
|
||||
ror ecx, 16 ; swap upper and lower 16-bits
|
||||
cmp eax, ecx ; are the upper and lower 16-bits the same?
|
||||
jnz .LAlreadyLocked ; If not return failure
|
||||
|
||||
# at this point we know eax+ecx have [avail][active] and they are both the same
|
||||
add ecx, 0x10000 # increment avail, ecx is now our wanted value
|
||||
lock cmpxchg [rdi+64], ecx # If rdi still contains the value in eax, put in ecx (inc avail)
|
||||
jnz .LAlreadyLocked # If Z is not set then someone locked it while we were preparing
|
||||
; at this point we know eax+ecx have [avail][active] and they are both the same
|
||||
add ecx, 10000h ; increment avail, ecx is now our wanted value
|
||||
lock cmpxchg [rdi], ecx ; If rdi still contains the value in eax, put in ecx (inc avail)
|
||||
jnz .LAlreadyLocked ; If Z is not set then someone locked it while we were preparing
|
||||
xor eax, eax
|
||||
inc eax # return SUCCESS! (eax=1)
|
||||
mov [rdi], esi # lock->m_pidOwner = gettid()
|
||||
mov dword ptr [rdi+4], eax # lock->m_depth = 1
|
||||
inc eax ; return SUCCESS! (eax=1)
|
||||
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
|
||||
mov dword [rdi+8], eax ; lock->m_depth = 1
|
||||
ret
|
||||
.ALIGN 16
|
||||
ALIGN 16
|
||||
.LRecursive:
|
||||
xor eax, eax
|
||||
inc eax # return SUCCESS! (eax=1)
|
||||
inc dword ptr [rdi+4] # lock->m_depth++
|
||||
inc eax ; return SUCCESS! (eax=1)
|
||||
inc dword [rdi+8] ; lock->m_depth++
|
||||
ret
|
||||
.ALIGN 16
|
||||
ALIGN 16
|
||||
.LAlreadyLocked:
|
||||
xor eax, eax # return 0
|
||||
xor eax, eax ; return 0;
|
||||
ret
|
||||
|
||||
.ALIGN 16
|
||||
.global fastlock_unlock
|
||||
.type fastlock_unlock,@function
|
||||
ALIGN 16
|
||||
global fastlock_unlock
|
||||
fastlock_unlock:
|
||||
# RDI points to the struct:
|
||||
# int32_t m_pidOwner
|
||||
# int32_t m_depth
|
||||
# [rdi+64] ...
|
||||
# uint16_t active
|
||||
# uint16_t avail
|
||||
sub dword ptr [rdi+4], 1 # decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
|
||||
jnz .LDone # if depth is non-zero this is a recursive unlock, and we still hold it
|
||||
mov dword ptr [rdi], -1 # pidOwner = -1 (we don't own it anymore)
|
||||
mov esi, [rdi+64] # get current active (this one)
|
||||
inc esi # bump it to the next thread
|
||||
mov word ptr [rdi+64], si # give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
|
||||
mfence # sync other threads
|
||||
# At this point the lock is removed, however we must wake up any pending futexs
|
||||
mov edx, [rdi+64+4] # load the futex mask
|
||||
bt edx, esi # is the next thread waiting on a futex?
|
||||
jc unlock_futex # unlock the futex if necessary
|
||||
ret # if not we're done.
|
||||
.ALIGN 16
|
||||
; RDI points to the struct:
|
||||
; uint16_t active
|
||||
; uint16_t avail
|
||||
; int32_t m_pidOwner
|
||||
; int32_t m_depth
|
||||
push r11
|
||||
sub dword [rdi+8], 1 ; decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
|
||||
jnz .LDone ; if depth is non-zero this is a recursive unlock, and we still hold it
|
||||
mov dword [rdi+4], -1 ; pidOwner = -1 (we don't own it anymore)
|
||||
mov ecx, [rdi] ; get current active (this one)
|
||||
inc ecx ; bump it to the next thread
|
||||
mov [rdi], cx ; give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
|
||||
; At this point the lock is removed, however we must wake up any pending futexs
|
||||
mov r9d, 1 ; eax is the bitmask for 2 threads
|
||||
rol r9d, cl ; place the mask in the right spot for the next 2 threads
|
||||
ALIGN 16
|
||||
.LRetryWake:
|
||||
mov r11d, [rdi+12] ; load the futex mask
|
||||
and r11d, r9d ; are any threads waiting on a futex?
|
||||
jz .LDone ; if not we're done.
|
||||
; we have to wake the futexs
|
||||
; rdi ARG1 futex (already in rdi)
|
||||
mov esi, (10 | 128) ; rsi ARG2 FUTEX_WAKE_BITSET_PRIVATE
|
||||
mov edx, 0x7fffffff ; rdx ARG3 INT_MAX (number of threads to wake)
|
||||
xor r10d, r10d ; r10 ARG4 NULL
|
||||
mov r8, rdi ; r8 ARG5 dup rdi
|
||||
; r9 ARG6 mask (already set above)
|
||||
mov eax, 202 ; sys_futex
|
||||
syscall
|
||||
cmp eax, 1 ; did we wake as many as we expected?
|
||||
jnz .LRetryWake
|
||||
.LDone:
|
||||
js fastlock_panic # panic if we made m_depth negative
|
||||
pop r11
|
||||
ret
|
||||
+1
-7
@@ -1168,13 +1168,7 @@ struct commandHelp {
|
||||
"destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]",
|
||||
"Add multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"2.0.0" },
|
||||
{ "KEYDB.CRON",
|
||||
"name [single/repeat] [optional: start] delay script numkeys [key N] [arg N]",
|
||||
"Run a specified script after start + delay, optionally repeating every delay interval. The job may be cancelled by deleting the key associated with the job (name parameter)",
|
||||
10,
|
||||
"6.5.2"
|
||||
}
|
||||
"2.0.0" }
|
||||
};
|
||||
|
||||
#endif
|
||||
+1
-1
@@ -4043,7 +4043,7 @@ int moduleGILAcquiredByModule(void) {
|
||||
* used to send anything to the client, and has the db number where the event
|
||||
* occurred as its selected db number.
|
||||
*
|
||||
* Notice that it is not necessary to enable notifications in keydb.conf for
|
||||
* Notice that it is not necessary to enable notifications in redis.conf for
|
||||
* module notifications to work.
|
||||
*
|
||||
* Warning: the notification callbacks are performed in a synchronous manner,
|
||||
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
#include "sds.h"
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "motd.h"
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Message of the day
|
||||
*--------------------------------------------------------------------------- */
|
||||
#ifdef MOTD
|
||||
#include <curl/curl.h>
|
||||
|
||||
static const char *szMotdCachePath()
|
||||
{
|
||||
static sds sdsMotdCachePath = NULL;
|
||||
if (sdsMotdCachePath != NULL)
|
||||
return sdsMotdCachePath;
|
||||
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw == NULL)
|
||||
return "";
|
||||
const char *homedir = pw->pw_dir;
|
||||
sdsMotdCachePath = sdsnew(homedir);
|
||||
sdsMotdCachePath = sdscat(sdsMotdCachePath, motd_cache_file);
|
||||
return sdsMotdCachePath;
|
||||
}
|
||||
static size_t motd_write_callback(void *ptr, size_t size, size_t nmemb, sds *str)
|
||||
{
|
||||
*str = sdscatlen(*str, ptr, size*nmemb);
|
||||
return (size*nmemb);
|
||||
}
|
||||
|
||||
static char *fetchMOTDFromCache()
|
||||
{
|
||||
struct stat attrib;
|
||||
if (stat(szMotdCachePath(), &attrib) != 0)
|
||||
return NULL;
|
||||
time_t t = attrib.st_mtim.tv_sec;
|
||||
time_t now = time(NULL);
|
||||
if ((now - t) < 14400)
|
||||
{
|
||||
// If our cache was updated no more than 4 hours ago use it instead of fetching the MOTD
|
||||
FILE *pf = fopen(szMotdCachePath(), "rb");
|
||||
if (pf == NULL)
|
||||
return NULL;
|
||||
fseek(pf, 0L, SEEK_END);
|
||||
long cb = ftell(pf);
|
||||
fseek(pf, 0L, SEEK_SET); // rewind
|
||||
sds str = sdsnewlen(NULL, cb);
|
||||
size_t cbRead = fread(str, 1, cb, pf);
|
||||
fclose(pf);
|
||||
if ((long)cbRead != cb)
|
||||
{
|
||||
sdsfree(str);
|
||||
return NULL;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void setMOTDCache(const char *sz)
|
||||
{
|
||||
FILE *pf = fopen(szMotdCachePath(), "wb");
|
||||
if (pf == NULL)
|
||||
return;
|
||||
size_t celem = fwrite(sz, strlen(sz), 1, pf);
|
||||
(void)celem; // best effort
|
||||
fclose(pf);
|
||||
}
|
||||
|
||||
extern "C" char *fetchMOTD(int cache)
|
||||
{
|
||||
sds str;
|
||||
CURL *curl;
|
||||
CURLcode res;
|
||||
|
||||
/* First try and get the string from the cache */
|
||||
if (cache) {
|
||||
str = fetchMOTDFromCache();
|
||||
if (str != NULL)
|
||||
return str;
|
||||
}
|
||||
|
||||
str = sdsnew("");
|
||||
curl = curl_easy_init();
|
||||
if(curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, motd_url);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // follow redirects
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2); // take no more than two seconds
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, motd_write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
|
||||
|
||||
/* Perform the request, res will get the return code */
|
||||
res = curl_easy_perform(curl);
|
||||
/* Check for errors */
|
||||
if(res != CURLE_OK)
|
||||
{
|
||||
sdsfree(str);
|
||||
str = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
long response_code;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
if ((response_code / 100) != 2)
|
||||
{
|
||||
// An error code not in the 200s implies an error
|
||||
sdsfree(str);
|
||||
str = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* always cleanup */
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (str != NULL && cache)
|
||||
setMOTDCache(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
extern "C" char *fetchMOTD(int /* cache */)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
extern const char *motd_url;
|
||||
extern const char *motd_cache_file;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
char *fetchMOTD(int fCache);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+2
-6
@@ -28,7 +28,6 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
bool FInReplicaReplay();
|
||||
|
||||
/* ================================ MULTI/EXEC ============================== */
|
||||
|
||||
@@ -173,15 +172,12 @@ void execCommand(client *c) {
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)) && !(FInReplicaReplay())) {
|
||||
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN))) {
|
||||
execCommandPropagateMulti(c);
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
int flags = g_pserver->loading ? CMD_CALL_NONE : CMD_CALL_FULL;
|
||||
if (FInReplicaReplay())
|
||||
flags &= ~CMD_CALL_PROPAGATE;
|
||||
call(c,flags);
|
||||
call(c,g_pserver->loading ? CMD_CALL_NONE : CMD_CALL_FULL);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
c->mstate.commands[j].argc = c->argc;
|
||||
|
||||
+21
-150
@@ -99,7 +99,6 @@ client *createClient(int fd, int iel) {
|
||||
* in the context of a client. When commands are executed in other
|
||||
* contexts (for instance a Lua script) we need a non connected client. */
|
||||
if (fd != -1) {
|
||||
serverAssert(iel == (serverTL - g_pserver->rgthreadvar));
|
||||
anetNonBlock(NULL,fd);
|
||||
anetEnableTcpNoDelay(NULL,fd);
|
||||
if (cserver.tcpkeepalive)
|
||||
@@ -117,7 +116,7 @@ client *createClient(int fd, int iel) {
|
||||
uint64_t client_id;
|
||||
client_id = g_pserver->next_client_id.fetch_add(1);
|
||||
c->iel = iel;
|
||||
fastlock_init(&c->lock, "client");
|
||||
fastlock_init(&c->lock);
|
||||
c->id = client_id;
|
||||
c->resp = 2;
|
||||
c->fd = fd;
|
||||
@@ -177,7 +176,6 @@ client *createClient(int fd, int iel) {
|
||||
c->bufposAsync = 0;
|
||||
c->client_tracking_redirection = 0;
|
||||
c->casyncOpsPending = 0;
|
||||
c->master_error = 0;
|
||||
memset(c->uuid, 0, UUID_BINARY_LEN);
|
||||
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
@@ -250,11 +248,7 @@ void clientInstallAsyncWriteHandler(client *c) {
|
||||
int prepareClientToWrite(client *c, bool fAsync) {
|
||||
fAsync = fAsync && !FCorrectThread(c); // Not async if we're on the right thread
|
||||
serverAssert(FCorrectThread(c) || fAsync);
|
||||
if (FCorrectThread(c)) {
|
||||
serverAssert(c->fd <= 0 || c->lock.fOwnLock());
|
||||
} else {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
}
|
||||
serverAssert(c->fd <= 0 || c->lock.fOwnLock());
|
||||
|
||||
if (c->flags & CLIENT_FORCE_REPLY) return C_OK; // FORCE REPLY means we're doing something else with the buffer.
|
||||
// do not install a write handler
|
||||
@@ -433,34 +427,6 @@ void addReplyProtoAsync(client *c, const char *s, size_t len) {
|
||||
addReplyProtoCore(c, s, len, true);
|
||||
}
|
||||
|
||||
std::string escapeString(sds str)
|
||||
{
|
||||
std::string newstr;
|
||||
size_t len = sdslen(str);
|
||||
for (size_t ich = 0; ich < len; ++ich)
|
||||
{
|
||||
char ch = str[ich];
|
||||
switch (ch)
|
||||
{
|
||||
case '\n':
|
||||
newstr += "\\n";
|
||||
break;
|
||||
|
||||
case '\t':
|
||||
newstr += "\\t";
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
newstr += "\\r";
|
||||
break;
|
||||
|
||||
default:
|
||||
newstr += ch;
|
||||
}
|
||||
}
|
||||
return newstr;
|
||||
}
|
||||
|
||||
/* Low level function called by the addReplyError...() functions.
|
||||
* It emits the protocol for a Redis error, in the form:
|
||||
*
|
||||
@@ -493,12 +459,6 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
|
||||
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
|
||||
"to its %s: '%s' after processing the command "
|
||||
"'%s'", from, to, s, cmdname);
|
||||
|
||||
if (c->querybuf && sdslen(c->querybuf)) {
|
||||
std::string str = escapeString(c->querybuf);
|
||||
serverLog(LL_WARNING, "\tquerybuf: %s", str.c_str());
|
||||
}
|
||||
c->master_error = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1038,27 +998,6 @@ int clientHasPendingReplies(client *c) {
|
||||
return (c->bufpos || listLength(c->reply)) && !(c->flags & CLIENT_CLOSE_ASAP);
|
||||
}
|
||||
|
||||
static std::atomic<int> rgacceptsInFlight[MAX_EVENT_LOOPS];
|
||||
int chooseBestThreadForAccept()
|
||||
{
|
||||
int ielMinLoad = 0;
|
||||
int cclientsMin = INT_MAX;
|
||||
for (int iel = 0; iel < cserver.cthreads; ++iel)
|
||||
{
|
||||
int cclientsThread;
|
||||
atomicGet(g_pserver->rgthreadvar[iel].cclients, cclientsThread);
|
||||
cclientsThread += rgacceptsInFlight[iel].load(std::memory_order_relaxed);
|
||||
if (cclientsThread < cserver.thread_min_client_threshold)
|
||||
return iel;
|
||||
if (cclientsThread < cclientsMin)
|
||||
{
|
||||
cclientsMin = cclientsThread;
|
||||
ielMinLoad = iel;
|
||||
}
|
||||
}
|
||||
return ielMinLoad;
|
||||
}
|
||||
|
||||
#define MAX_ACCEPTS_PER_CALL 1000
|
||||
static void acceptCommonHandler(int fd, int flags, char *ip, int iel) {
|
||||
client *c;
|
||||
@@ -1161,25 +1100,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
if (!g_fTestMode)
|
||||
{
|
||||
{
|
||||
int ielTarget = chooseBestThreadForAccept();
|
||||
rgacceptsInFlight[ielTarget].fetch_add(1, std::memory_order_relaxed);
|
||||
if (ielTarget != ielCur)
|
||||
{
|
||||
char *szT = (char*)zmalloc(NET_IP_STR_LEN, MALLOC_LOCAL);
|
||||
memcpy(szT, cip, NET_IP_STR_LEN);
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [cfd, ielTarget, szT]{
|
||||
acceptCommonHandler(cfd,0,szT, ielTarget);
|
||||
rgacceptsInFlight[ielTarget].fetch_sub(1, std::memory_order_relaxed);
|
||||
zfree(szT);
|
||||
});
|
||||
|
||||
if (res == AE_OK)
|
||||
continue;
|
||||
}
|
||||
rgacceptsInFlight[ielTarget].fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// We always accept on the same thread
|
||||
LLocalThread:
|
||||
aeAcquireLock();
|
||||
acceptCommonHandler(cfd,0,cip, ielCur);
|
||||
@@ -1196,15 +1117,10 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
goto LLocalThread;
|
||||
char *szT = (char*)zmalloc(NET_IP_STR_LEN, MALLOC_LOCAL);
|
||||
memcpy(szT, cip, NET_IP_STR_LEN);
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[iel].el, [cfd, iel, szT]{
|
||||
aePostFunction(g_pserver->rgthreadvar[iel].el, [cfd, iel, szT]{
|
||||
acceptCommonHandler(cfd,0,szT, iel);
|
||||
zfree(szT);
|
||||
});
|
||||
if (res != AE_OK)
|
||||
{
|
||||
zfree(szT);
|
||||
goto LLocalThread;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1230,16 +1146,13 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int ielTarget = rand() % cserver.cthreads;
|
||||
if (ielTarget == ielCur)
|
||||
{
|
||||
LLocalThread:
|
||||
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL, ielCur);
|
||||
}
|
||||
else
|
||||
{
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [cfd, ielTarget]{
|
||||
aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [cfd, ielTarget]{
|
||||
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL, ielTarget);
|
||||
});
|
||||
if (res != AE_OK)
|
||||
goto LLocalThread;
|
||||
}
|
||||
aeReleaseLock();
|
||||
|
||||
@@ -1596,6 +1509,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
} else {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error writing to client: %s", strerror(errno));
|
||||
lock.unlock();
|
||||
freeClientAsync(c);
|
||||
|
||||
return C_ERR;
|
||||
@@ -1614,6 +1528,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
|
||||
/* Close connection after entire reply has been sent. */
|
||||
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
|
||||
lock.unlock();
|
||||
freeClientAsync(c);
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -2604,17 +2519,9 @@ NULL
|
||||
close_this_client = 1;
|
||||
} else {
|
||||
if (FCorrectThread(client))
|
||||
{
|
||||
freeClient(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
int iel = client->iel;
|
||||
freeClientAsync(client);
|
||||
aePostFunction(g_pserver->rgthreadvar[client->iel].el, [iel] { // note: failure is OK
|
||||
freeClientsInAsyncFreeQueue(iel);
|
||||
});
|
||||
}
|
||||
}
|
||||
killed++;
|
||||
}
|
||||
@@ -3043,48 +2950,38 @@ void flushSlavesOutputBuffers(void) {
|
||||
* than the time left for the previous pause, no change is made to the
|
||||
* left duration. */
|
||||
void pauseClients(mstime_t end) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
if (!serverTL->clients_paused || end > g_pserver->clients_pause_end_time)
|
||||
if (!g_pserver->clients_paused || end > g_pserver->clients_pause_end_time)
|
||||
g_pserver->clients_pause_end_time = end;
|
||||
|
||||
for (int iel = 0; iel < cserver.cthreads; ++iel)
|
||||
{
|
||||
g_pserver->rgthreadvar[iel].clients_paused = true;
|
||||
}
|
||||
g_pserver->clients_paused = 1;
|
||||
}
|
||||
|
||||
/* Return non-zero if clients are currently paused. As a side effect the
|
||||
* function checks if the pause time was reached and clear it. */
|
||||
int clientsArePaused(void) {
|
||||
return serverTL->clients_paused;
|
||||
}
|
||||
|
||||
void unpauseClientsIfNecessary()
|
||||
{
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
if (serverTL->clients_paused &&
|
||||
if (g_pserver->clients_paused &&
|
||||
g_pserver->clients_pause_end_time < g_pserver->mstime)
|
||||
{
|
||||
aeAcquireLock();
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
client *c;
|
||||
|
||||
serverTL->clients_paused = 0;
|
||||
g_pserver->clients_paused = 0;
|
||||
|
||||
/* Put all the clients in the unblocked clients queue in order to
|
||||
* force the re-processing of the input buffer if any. */
|
||||
listRewind(g_pserver->clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
c = (client*)listNodeValue(ln);
|
||||
if (!FCorrectThread(c))
|
||||
continue;
|
||||
|
||||
/* Don't touch slaves and blocked clients.
|
||||
* The latter pending requests will be processed when unblocked. */
|
||||
if (c->flags & (CLIENT_SLAVE|CLIENT_BLOCKED)) continue;
|
||||
queueClientForReprocessing(c);
|
||||
}
|
||||
aeReleaseLock();
|
||||
}
|
||||
return g_pserver->clients_paused;
|
||||
}
|
||||
|
||||
/* This function is called by Redis in order to process a few events from
|
||||
@@ -3100,44 +2997,18 @@ void unpauseClientsIfNecessary()
|
||||
*
|
||||
* The function returns the total number of events processed. */
|
||||
int processEventsWhileBlocked(int iel) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
int iterations = 4; /* See the function top-comment. */
|
||||
int count = 0;
|
||||
|
||||
client *c = serverTL->current_client;
|
||||
if (c != nullptr)
|
||||
{
|
||||
serverAssert(c->flags & CLIENT_PROTECTED);
|
||||
c->lock.unlock();
|
||||
}
|
||||
|
||||
aeReleaseLock();
|
||||
try
|
||||
{
|
||||
while (iterations--) {
|
||||
int events = 0;
|
||||
events += aeProcessEvents(g_pserver->rgthreadvar[iel].el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
events += handleClientsWithPendingWrites(iel);
|
||||
if (!events) break;
|
||||
count += events;
|
||||
}
|
||||
while (iterations--) {
|
||||
int events = 0;
|
||||
events += aeProcessEvents(g_pserver->rgthreadvar[iel].el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
events += handleClientsWithPendingWrites(iel);
|
||||
if (!events) break;
|
||||
count += events;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Caller expects us to be locked so fix and rethrow
|
||||
AeLocker locker;
|
||||
if (c != nullptr)
|
||||
c->lock.lock();
|
||||
locker.arm(c);
|
||||
locker.release();
|
||||
throw;
|
||||
}
|
||||
|
||||
AeLocker locker;
|
||||
if (c != nullptr)
|
||||
c->lock.lock();
|
||||
locker.arm(c);
|
||||
locker.release();
|
||||
aeAcquireLock();
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "cron.h"
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -370,7 +369,6 @@ void decrRefCount(robj_roptr o) {
|
||||
case OBJ_HASH: freeHashObject(o); break;
|
||||
case OBJ_MODULE: freeModuleObject(o); break;
|
||||
case OBJ_STREAM: freeStreamObject(o); break;
|
||||
case OBJ_CRON: freeCronObject(o); break;
|
||||
default: serverPanic("Unknown object type"); break;
|
||||
}
|
||||
zfree(o.unsafe_robjcast());
|
||||
@@ -716,34 +714,6 @@ int getLongLongFromObject(robj *o, long long *target) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
int getUnsignedLongLongFromObject(robj *o, uint64_t *target) {
|
||||
uint64_t value;
|
||||
|
||||
if (o == NULL) {
|
||||
value = 0;
|
||||
} else {
|
||||
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
char *pchEnd = nullptr;
|
||||
errno = 0;
|
||||
value = strtoull(szFromObj(o), &pchEnd, 10);
|
||||
if (value == 0) {
|
||||
// potential error
|
||||
if (errno != 0)
|
||||
return C_ERR;
|
||||
if (pchEnd == szFromObj(o))
|
||||
return C_ERR;
|
||||
}
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)ptrFromObj(o);
|
||||
} else {
|
||||
serverPanic("Unknown string encoding");
|
||||
}
|
||||
}
|
||||
if (target) *target = value;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
int getLongLongFromObjectOrReply(client *c, robj *o, long long *target, const char *msg) {
|
||||
long long value;
|
||||
if (getLongLongFromObject(o, &value) != C_OK) {
|
||||
|
||||
+4
-8
@@ -301,11 +301,9 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
client *c = reinterpret_cast<client*>(ln->value);
|
||||
if (c->flags & CLIENT_CLOSE_ASAP) // avoid blocking if the write will be ignored
|
||||
continue;
|
||||
if (FCorrectThread(c))
|
||||
fastlock_lock(&c->lock);
|
||||
fastlock_lock(&c->lock);
|
||||
addReplyPubsubMessage(c,channel,message);
|
||||
if (FCorrectThread(c))
|
||||
fastlock_unlock(&c->lock);
|
||||
fastlock_unlock(&c->lock);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
@@ -323,12 +321,10 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
{
|
||||
if (pat->pclient->flags & CLIENT_CLOSE_ASAP)
|
||||
continue;
|
||||
if (FCorrectThread(pat->pclient))
|
||||
fastlock_lock(&pat->pclient->lock);
|
||||
fastlock_lock(&pat->pclient->lock);
|
||||
addReplyPubsubPatMessage(pat->pclient,
|
||||
pat->pattern,channel,message);
|
||||
if (FCorrectThread(pat->pclient))
|
||||
fastlock_unlock(&pat->pclient->lock);
|
||||
fastlock_unlock(&pat->pclient->lock);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-50
@@ -33,7 +33,6 @@
|
||||
#include "endianconv.h"
|
||||
#include "stream.h"
|
||||
#include "storage.h"
|
||||
#include "cron.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <sys/types.h>
|
||||
@@ -524,11 +523,6 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
|
||||
}
|
||||
}
|
||||
|
||||
sdsstring rdbLoadString(rio *rdb){
|
||||
sds str = (sds)rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
|
||||
return sdsstring(str);
|
||||
}
|
||||
|
||||
robj *rdbLoadStringObject(rio *rdb) {
|
||||
return (robj*)rdbGenericLoadStringObject(rdb,RDB_LOAD_NONE,NULL);
|
||||
}
|
||||
@@ -663,8 +657,6 @@ int rdbSaveObjectType(rio *rdb, robj_roptr o) {
|
||||
return rdbSaveType(rdb,RDB_TYPE_STREAM_LISTPACKS);
|
||||
case OBJ_MODULE:
|
||||
return rdbSaveType(rdb,RDB_TYPE_MODULE_2);
|
||||
case OBJ_CRON:
|
||||
return rdbSaveType(rdb,RDB_TYPE_CRON);
|
||||
default:
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
@@ -994,17 +986,6 @@ ssize_t rdbSaveObject(rio *rdb, robj_roptr o, robj *key) {
|
||||
zfree(io.ctx);
|
||||
}
|
||||
return io.error ? -1 : (ssize_t)io.bytes;
|
||||
} else if (o->type == OBJ_CRON) {
|
||||
cronjob *job = (cronjob*)ptrFromObj(o);
|
||||
rdbSaveRawString(rdb, (const unsigned char*)job->script.get(), job->script.size());
|
||||
rdbSaveMillisecondTime(rdb, job->startTime);
|
||||
rdbSaveMillisecondTime(rdb, job->interval);
|
||||
rdbSaveLen(rdb, job->veckeys.size());
|
||||
for (auto &key : job->veckeys)
|
||||
rdbSaveRawString(rdb, (const unsigned char*)key.get(), key.size());
|
||||
rdbSaveLen(rdb, job->vecargs.size());
|
||||
for (auto &arg : job->vecargs)
|
||||
rdbSaveRawString(rdb, (const unsigned char*)arg.get(), arg.size());
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
@@ -1545,15 +1526,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
== NULL) return NULL;
|
||||
|
||||
if (rdbtype == RDB_TYPE_ZSET_2) {
|
||||
if (rdbLoadBinaryDoubleValue(rdb,&score) == -1) {
|
||||
sdsfree(sdsele);
|
||||
return NULL;
|
||||
}
|
||||
if (rdbLoadBinaryDoubleValue(rdb,&score) == -1) return NULL;
|
||||
} else {
|
||||
if (rdbLoadDoubleValue(rdb,&score) == -1) {
|
||||
sdsfree(sdsele);
|
||||
return NULL;
|
||||
}
|
||||
if (rdbLoadDoubleValue(rdb,&score) == -1) return NULL;
|
||||
}
|
||||
|
||||
/* Don't care about integer-encoded strings. */
|
||||
@@ -1873,18 +1848,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key, uint64_t mvcc_tstamp) {
|
||||
exit(1);
|
||||
}
|
||||
o = createModuleObject(mt,ptr);
|
||||
} else if (rdbtype == RDB_TYPE_CRON) {
|
||||
std::unique_ptr<cronjob> spjob = std::make_unique<cronjob>();
|
||||
spjob->script = rdbLoadString(rdb);
|
||||
spjob->startTime = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
spjob->interval = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
auto ckeys = rdbLoadLen(rdb,NULL);
|
||||
for (uint64_t i = 0; i < ckeys; ++i)
|
||||
spjob->veckeys.push_back(rdbLoadString(rdb));
|
||||
auto cargs = rdbLoadLen(rdb,NULL);
|
||||
for (uint64_t i = 0; i < cargs; ++i)
|
||||
spjob->vecargs.push_back(rdbLoadString(rdb));
|
||||
o = createObject(OBJ_CRON, spjob.release());
|
||||
} else {
|
||||
rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
|
||||
}
|
||||
@@ -1956,7 +1919,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
redisDb *db = g_pserver->db+0;
|
||||
char buf[1024];
|
||||
/* Key-specific attributes, set by opcodes before the key type. */
|
||||
long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now;
|
||||
long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now = mstime();
|
||||
long long lru_clock = 0;
|
||||
uint64_t mvcc_tstamp = OBJ_MVCC_INVALID;
|
||||
robj *subexpireKey = nullptr;
|
||||
@@ -2152,19 +2115,12 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb,key, mvcc_tstamp)) == NULL) goto eoferr;
|
||||
bool fStaleMvccKey = val->mvcc_tstamp < rsi->mvccMinThreshold;
|
||||
/* 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 replica. */
|
||||
bool fExpiredKey = (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica) && !loading_aof && expiretime != -1 && expiretime < now;
|
||||
if (fStaleMvccKey || fExpiredKey) {
|
||||
if (fStaleMvccKey && !fExpiredKey && rsi->mi != nullptr && rsi->mi->staleKeyMap != nullptr && lookupKeyRead(db, key) == nullptr) {
|
||||
// We have a key that we've already deleted and is not back in our database.
|
||||
// We'll need to inform the sending master of the delete if it is also a replica of us
|
||||
rsi->mi->staleKeyMap->operator[](db - g_pserver->db).push_back(key);
|
||||
}
|
||||
if (listLength(g_pserver->masters) == 0 && !loading_aof && expiretime != -1 && expiretime < now) {
|
||||
decrRefCount(key);
|
||||
key = nullptr;
|
||||
decrRefCount(val);
|
||||
@@ -2187,8 +2143,6 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
decrRefCount(val);
|
||||
val = nullptr;
|
||||
}
|
||||
decrRefCount(key);
|
||||
key = nullptr;
|
||||
}
|
||||
|
||||
/* Reset the state that is key-specified and is populated by
|
||||
|
||||
@@ -91,13 +91,10 @@
|
||||
#define RDB_TYPE_HASH_ZIPLIST 13
|
||||
#define RDB_TYPE_LIST_QUICKLIST 14
|
||||
#define RDB_TYPE_STREAM_LISTPACKS 15
|
||||
|
||||
/* KeyDB Specific Object Types */
|
||||
#define RDB_TYPE_CRON 64
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||
|
||||
/* Test if a type is an object type. */
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 15) || (t == RDB_TYPE_CRON))
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 15))
|
||||
|
||||
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
||||
#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */
|
||||
|
||||
@@ -173,8 +173,6 @@ typedef struct redisConfig {
|
||||
sds appendonly;
|
||||
} redisConfig;
|
||||
|
||||
int g_fInCrash = false;
|
||||
|
||||
/* Prototypes */
|
||||
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
static void createMissingClients(client c);
|
||||
|
||||
+111
-7
@@ -57,7 +57,6 @@
|
||||
#include "anet.h"
|
||||
#include "ae.h"
|
||||
#include "storage.h"
|
||||
#include "motd.h"
|
||||
|
||||
#include "redis-cli.h"
|
||||
|
||||
@@ -91,11 +90,6 @@ int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253
|
||||
int *spectrum_palette;
|
||||
int spectrum_palette_size;
|
||||
|
||||
int g_fInCrash = 0;
|
||||
|
||||
const char *motd_url = "http://api.keydb.dev/motd/motd_cli.txt";
|
||||
const char *motd_cache_file = "/.keydb-cli-motd";
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Utility functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -6540,6 +6534,116 @@ static void intrinsicLatencyMode(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Message of the day
|
||||
*--------------------------------------------------------------------------- */
|
||||
#ifdef MOTD
|
||||
#include <curl/curl.h>
|
||||
|
||||
static const char *szMotdCachePath()
|
||||
{
|
||||
static sds sdsMotdCachePath = NULL;
|
||||
if (sdsMotdCachePath != NULL)
|
||||
return sdsMotdCachePath;
|
||||
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
if (pw == NULL)
|
||||
return "";
|
||||
const char *homedir = pw->pw_dir;
|
||||
sdsMotdCachePath = sdsnew(homedir);
|
||||
sdsMotdCachePath = sdscat(sdsMotdCachePath, "/.keydb-cli-motd");
|
||||
return sdsMotdCachePath;
|
||||
}
|
||||
static size_t motd_write_callback(void *ptr, size_t size, size_t nmemb, sds *str)
|
||||
{
|
||||
*str = sdscatlen(*str, ptr, size*nmemb);
|
||||
return (size*nmemb);
|
||||
}
|
||||
|
||||
static char *fetchMOTDFromCache()
|
||||
{
|
||||
struct stat attrib;
|
||||
if (stat(szMotdCachePath(), &attrib) != 0)
|
||||
return NULL;
|
||||
time_t t = attrib.st_mtim.tv_sec;
|
||||
time_t now = time(NULL);
|
||||
if ((now - t) < 14400)
|
||||
{
|
||||
// If our cache was updated no more than 4 hours ago use it instead of fetching the MOTD
|
||||
FILE *pf = fopen(szMotdCachePath(), "rb");
|
||||
if (pf == NULL)
|
||||
return NULL;
|
||||
fseek(pf, 0L, SEEK_END);
|
||||
long cb = ftell(pf);
|
||||
fseek(pf, 0L, SEEK_SET); // rewind
|
||||
sds str = sdsnewlen(NULL, cb);
|
||||
size_t cbRead = fread(str, 1, cb, pf);
|
||||
fclose(pf);
|
||||
if ((long)cbRead != cb)
|
||||
{
|
||||
sdsfree(str);
|
||||
return NULL;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void setMOTDCache(const char *sz)
|
||||
{
|
||||
FILE *pf = fopen(szMotdCachePath(), "wb");
|
||||
size_t celem = fwrite(sz, strlen(sz), 1, pf);
|
||||
(void)celem; // best effort
|
||||
fclose(pf);
|
||||
}
|
||||
|
||||
static char *fetchMOTD()
|
||||
{
|
||||
sds str;
|
||||
CURL *curl;
|
||||
CURLcode res;
|
||||
|
||||
/* First try and get the string from the cache */
|
||||
str = fetchMOTDFromCache();
|
||||
if (str != NULL)
|
||||
return str;
|
||||
|
||||
str = sdsnew("");
|
||||
curl = curl_easy_init();
|
||||
if(curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, "http://api.keydb.dev/motd/motd.txt");
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // follow redirects
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2); // take no more than two seconds
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, motd_write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str);
|
||||
|
||||
/* Perform the request, res will get the return code */
|
||||
res = curl_easy_perform(curl);
|
||||
/* Check for errors */
|
||||
if(res != CURLE_OK)
|
||||
{
|
||||
sdsfree(str);
|
||||
str = NULL;
|
||||
}
|
||||
|
||||
/* always cleanup */
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (str != NULL)
|
||||
setMOTDCache(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static char *fetchMOTD()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Program main()
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -6710,7 +6814,7 @@ int main(int argc, char **argv) {
|
||||
if (argc == 0 && !config.eval) {
|
||||
/* Show the message of the day if we are interactive */
|
||||
if (config.output == OUTPUT_STANDARD) {
|
||||
char *szMotd = fetchMOTD(1 /* cache */);
|
||||
char *szMotd = fetchMOTD();
|
||||
if (szMotd != NULL) {
|
||||
printf("Message of the day:\n %s\n", szMotd);
|
||||
sdsfree(szMotd);
|
||||
|
||||
+24
-221
@@ -42,15 +42,12 @@
|
||||
#include <algorithm>
|
||||
#include <uuid/uuid.h>
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
void replicationDiscardCachedMaster(redisMaster *mi);
|
||||
void replicationResurrectCachedMaster(redisMaster *mi, int newfd);
|
||||
void replicationSendAck(redisMaster *mi);
|
||||
void putSlaveOnline(client *replica);
|
||||
int cancelReplicationHandshake(redisMaster *mi);
|
||||
static void propagateMasterStaleKeys();
|
||||
|
||||
/* --------------------------- Utility functions ---------------------------- */
|
||||
|
||||
@@ -132,23 +129,6 @@ static bool FAnyDisconnectedMasters()
|
||||
return false;
|
||||
}
|
||||
|
||||
client *replicaFromMaster(redisMaster *mi)
|
||||
{
|
||||
if (mi->master == nullptr)
|
||||
return nullptr;
|
||||
|
||||
listIter liReplica;
|
||||
listNode *lnReplica;
|
||||
listRewind(g_pserver->slaves, &liReplica);
|
||||
while ((lnReplica = listNext(&liReplica)) != nullptr)
|
||||
{
|
||||
client *replica = (client*)listNodeValue(lnReplica);
|
||||
if (FSameHost(mi->master, replica))
|
||||
return replica;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* ---------------------------------- MASTER -------------------------------- */
|
||||
|
||||
void createReplicationBacklog(void) {
|
||||
@@ -278,7 +258,7 @@ void replicationFeedSlave(client *replica, int dictid, robj **argv, int argc, bo
|
||||
if (g_pserver->repl_backlog && fSendRaw) feedReplicationBacklogWithObject(selectcmd);
|
||||
|
||||
/* Send it to slaves */
|
||||
addReplyAsync(replica,selectcmd);
|
||||
addReply(replica,selectcmd);
|
||||
|
||||
if (dictid < 0 || dictid >= PROTO_SHARED_SELECT_CMDS)
|
||||
decrRefCount(selectcmd);
|
||||
@@ -290,12 +270,12 @@ void replicationFeedSlave(client *replica, int dictid, robj **argv, int argc, bo
|
||||
* or are already in sync with the master. */
|
||||
|
||||
/* Add the multi bulk length. */
|
||||
addReplyArrayLenAsync(replica,argc);
|
||||
addReplyArrayLen(replica,argc);
|
||||
|
||||
/* Finally any additional argument that was not stored inside the
|
||||
* static buffer if any (from j to argc). */
|
||||
for (int j = 0; j < argc; j++)
|
||||
addReplyBulkAsync(replica,argv[j]);
|
||||
addReplyBulk(replica,argv[j]);
|
||||
}
|
||||
|
||||
/* Propagate write commands to slaves, and populate the replication backlog
|
||||
@@ -345,21 +325,12 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
char uuid[40] = {'\0'};
|
||||
uuid_unparse(cserver.uuid, uuid);
|
||||
char proto[1024];
|
||||
int cchProto = snprintf(proto, sizeof(proto), "*5\r\n$7\r\nRREPLAY\r\n$%d\r\n%s\r\n$%lld\r\n", (int)strlen(uuid), uuid, cchbuf);
|
||||
int cchProto = snprintf(proto, sizeof(proto), "*4\r\n$7\r\nRREPLAY\r\n$%d\r\n%s\r\n$%lld\r\n", (int)strlen(uuid), uuid, cchbuf);
|
||||
cchProto = std::min((int)sizeof(proto), cchProto);
|
||||
long long master_repl_offset_start = g_pserver->master_repl_offset;
|
||||
|
||||
char szDbNum[128];
|
||||
int cchDictIdNum = snprintf(szDbNum, sizeof(szDbNum), "%d", dictid);
|
||||
int cchDbNum = snprintf(szDbNum, sizeof(szDbNum), "$%d\r\n%d\r\n", cchDictIdNum, dictid);
|
||||
cchDbNum = std::min<int>(cchDbNum, sizeof(szDbNum)); // snprintf is tricky like that
|
||||
|
||||
char szMvcc[128];
|
||||
incrementMvccTstamp();
|
||||
uint64_t mvccTstamp = getMvccTstamp();
|
||||
int cchMvccNum = snprintf(szMvcc, sizeof(szMvcc), "%lu", mvccTstamp);
|
||||
int cchMvcc = snprintf(szMvcc, sizeof(szMvcc), "$%d\r\n%lu\r\n", cchMvccNum, mvccTstamp);
|
||||
cchMvcc = std::min<int>(cchMvcc, sizeof(szMvcc)); // tricky snprintf
|
||||
int cchDbNum = snprintf(szDbNum, sizeof(szDbNum), "$%d\r\n%d\r\n", (dictid/10)+1, dictid);
|
||||
|
||||
/* Write the command to the replication backlog if any. */
|
||||
if (g_pserver->repl_backlog)
|
||||
@@ -403,7 +374,6 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
const char *crlf = "\r\n";
|
||||
feedReplicationBacklog(crlf, 2);
|
||||
feedReplicationBacklog(szDbNum, cchDbNum);
|
||||
feedReplicationBacklog(szMvcc, cchMvcc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,11 +384,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
|
||||
/* Don't feed slaves that are still waiting for BGSAVE to start */
|
||||
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
|
||||
if (replica->flags & CLIENT_CLOSE_ASAP) continue;
|
||||
std::unique_lock<decltype(replica->lock)> lock(replica->lock, std::defer_lock);
|
||||
// When writing to clients on other threads the global lock is sufficient provided we only use AddReply*Async()
|
||||
if (FCorrectThread(replica))
|
||||
lock.lock();
|
||||
std::unique_lock<decltype(replica->lock)> lock(replica->lock);
|
||||
if (serverTL->current_client && FSameHost(serverTL->current_client, replica))
|
||||
{
|
||||
replica->reploff_skipped += g_pserver->master_repl_offset - master_repl_offset_start;
|
||||
@@ -435,12 +401,10 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
clientReplyBlock* reply = (clientReplyBlock*)listNodeValue(lnReply);
|
||||
addReplyProtoAsync(replica, reply->buf(), reply->used);
|
||||
}
|
||||
|
||||
if (!fSendRaw)
|
||||
{
|
||||
addReplyAsync(replica,shared.crlf);
|
||||
addReplyProtoAsync(replica, szDbNum, cchDbNum);
|
||||
addReplyProtoAsync(replica, szMvcc, cchMvcc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,9 +433,7 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)ln->value;
|
||||
std::unique_lock<decltype(replica->lock)> ulock(replica->lock, std::defer_lock);
|
||||
if (FCorrectThread(replica))
|
||||
ulock.lock();
|
||||
std::lock_guard<decltype(replica->lock)> ulock(replica->lock);
|
||||
if (FMasterHost(replica))
|
||||
continue; // Active Active case, don't feed back
|
||||
|
||||
@@ -520,10 +482,7 @@ void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv,
|
||||
listRewind(monitors,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *monitor = (client*)ln->value;
|
||||
std::unique_lock<decltype(monitor->lock)> lock(monitor->lock, std::defer_lock);
|
||||
// When writing to clients on other threads the global lock is sufficient provided we only use AddReply*Async()
|
||||
if (FCorrectThread(c))
|
||||
lock.lock();
|
||||
std::lock_guard<decltype(monitor->lock)> lock(monitor->lock);
|
||||
addReplyAsync(monitor,cmdobj);
|
||||
}
|
||||
decrRefCount(cmdobj);
|
||||
@@ -701,7 +660,6 @@ int masterTryPartialResynchronization(client *c) {
|
||||
c->repl_ack_time = g_pserver->unixtime;
|
||||
c->repl_put_online_on_ack = 0;
|
||||
listAddNodeTail(g_pserver->slaves,c);
|
||||
|
||||
/* We can't use the connection buffers since they are used to accumulate
|
||||
* new commands at this stage. But we are sure the socket send buffer is
|
||||
* empty so this write will never fail actually. */
|
||||
@@ -1035,8 +993,6 @@ void replconfCommand(client *c) {
|
||||
c->slave_capa |= SLAVE_CAPA_EOF;
|
||||
else if (!strcasecmp((const char*)ptrFromObj(c->argv[j+1]),"psync2"))
|
||||
c->slave_capa |= SLAVE_CAPA_PSYNC2;
|
||||
else if (!strcasecmp((const char*)ptrFromObj(c->argv[j+1]), "activeExpire"))
|
||||
c->slave_capa |= SLAVE_CAPA_ACTIVE_EXPIRE;
|
||||
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[j]),"ack")) {
|
||||
/* REPLCONF ACK is used by replica to inform the master the amount
|
||||
* of replication stream that it processed so far. It is an
|
||||
@@ -1106,14 +1062,6 @@ void putSlaveOnline(client *replica) {
|
||||
refreshGoodSlavesCount();
|
||||
serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
|
||||
replicationGetSlaveName(replica));
|
||||
|
||||
if (!(replica->slave_capa & SLAVE_CAPA_ACTIVE_EXPIRE) && g_pserver->fActiveReplica)
|
||||
{
|
||||
serverLog(LL_WARNING, "Warning: replica %s does not support active expiration. This client may not correctly process key expirations."
|
||||
"\n\tThis is OK if you are in the process of an active upgrade.", replicationGetSlaveName(replica));
|
||||
serverLog(LL_WARNING, "Connections between active replicas and traditional replicas is deprecated. This will be refused in future versions."
|
||||
"\n\tPlease fix your replica topology");
|
||||
}
|
||||
}
|
||||
|
||||
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
@@ -1257,21 +1205,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type)
|
||||
}
|
||||
else
|
||||
{
|
||||
aePostFunction(g_pserver->rgthreadvar[replica->iel].el, [replica] {
|
||||
// Because the client could have been closed while the lambda waited to run we need to
|
||||
// verify the replica is still connected
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
bool fFound = false;
|
||||
while ((ln = listNext(&li))) {
|
||||
if (listNodeValue(ln) == replica) {
|
||||
fFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fFound)
|
||||
return;
|
||||
aePostFunction(g_pserver->rgthreadvar[replica->iel].el, [replica]{
|
||||
aeDeleteFileEvent(g_pserver->rgthreadvar[replica->iel].el,replica->fd,AE_WRITABLE);
|
||||
if (aeCreateFileEvent(g_pserver->rgthreadvar[replica->iel].el, replica->fd, AE_WRITABLE, sendBulkToSlave, replica) == AE_ERR) {
|
||||
freeClient(replica);
|
||||
@@ -1619,15 +1553,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
aeDeleteFileEvent(el,mi->repl_transfer_s,AE_READABLE);
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
|
||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||
if (g_pserver->fActiveReplica)
|
||||
{
|
||||
rsi.mvccMinThreshold = mi->mvccLastSync;
|
||||
if (mi->staleKeyMap != nullptr)
|
||||
mi->staleKeyMap->clear();
|
||||
else
|
||||
mi->staleKeyMap = new (MALLOC_LOCAL) std::map<int, std::vector<robj_sharedptr>>();
|
||||
rsi.mi = mi;
|
||||
}
|
||||
if (rdbLoadFile(rdb_filename, &rsi) != C_OK) {
|
||||
serverLog(LL_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
|
||||
cancelReplicationHandshake(mi);
|
||||
@@ -2146,16 +2071,8 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
*
|
||||
* The master will ignore capabilities it does not understand. */
|
||||
if (mi->repl_state == REPL_STATE_SEND_CAPA) {
|
||||
if (g_pserver->fActiveReplica)
|
||||
{
|
||||
err = sendSynchronousCommand(mi, SYNC_CMD_WRITE,fd,"REPLCONF",
|
||||
"capa","eof","capa","psync2","capa","activeExpire",NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
err = sendSynchronousCommand(mi, SYNC_CMD_WRITE,fd,"REPLCONF",
|
||||
"capa","eof","capa","psync2",NULL);
|
||||
}
|
||||
err = sendSynchronousCommand(mi, SYNC_CMD_WRITE,fd,"REPLCONF",
|
||||
"capa","eof","capa","psync2",NULL);
|
||||
if (err) goto write_error;
|
||||
sdsfree(err);
|
||||
mi->repl_state = REPL_STATE_RECEIVE_CAPA;
|
||||
@@ -2302,8 +2219,7 @@ int connectWithMaster(redisMaster *mi) {
|
||||
fd = anetTcpNonBlockBestEffortBindConnect(NULL,
|
||||
mi->masterhost,mi->masterport,NET_FIRST_BIND_ADDR);
|
||||
if (fd == -1) {
|
||||
int sev = g_pserver->enable_multimaster ? LL_NOTICE : LL_WARNING; // with multimaster its not unheard of to intentiallionall have downed masters
|
||||
serverLog(sev,"Unable to connect to MASTER: %s",
|
||||
serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -2329,11 +2245,10 @@ int connectWithMaster(redisMaster *mi) {
|
||||
void undoConnectWithMaster(redisMaster *mi) {
|
||||
int fd = mi->repl_transfer_s;
|
||||
|
||||
int res = aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fd]{
|
||||
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fd]{
|
||||
aeDeleteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,fd,AE_READABLE|AE_WRITABLE);
|
||||
close(fd);
|
||||
});
|
||||
serverAssert(res == AE_OK);
|
||||
mi->repl_transfer_s = -1;
|
||||
}
|
||||
|
||||
@@ -2425,9 +2340,6 @@ void freeMasterInfo(redisMaster *mi)
|
||||
{
|
||||
zfree(mi->masterauth);
|
||||
zfree(mi->masteruser);
|
||||
if (mi->clientFake)
|
||||
freeClient(mi->clientFake);
|
||||
delete mi->staleKeyMap;
|
||||
zfree(mi);
|
||||
}
|
||||
|
||||
@@ -2484,11 +2396,6 @@ void replicationHandleMasterDisconnection(redisMaster *mi) {
|
||||
mi->master = NULL;
|
||||
mi->repl_state = REPL_STATE_CONNECT;
|
||||
mi->repl_down_since = g_pserver->unixtime;
|
||||
if (mi->clientFake) {
|
||||
freeClient(mi->clientFake);
|
||||
mi->clientFake = nullptr;
|
||||
|
||||
}
|
||||
/* We lost connection with our master, don't disconnect slaves yet,
|
||||
* maybe we'll be able to PSYNC with our master later. We'll disconnect
|
||||
* the slaves only if we'll have to do a full resync with our master. */
|
||||
@@ -3069,12 +2976,12 @@ void replicationCron(void) {
|
||||
if (mi->masterhost && mi->repl_state == REPL_STATE_TRANSFER &&
|
||||
(time(NULL)-mi->repl_transfer_lastio) > g_pserver->repl_timeout)
|
||||
{
|
||||
serverLog(LL_WARNING,"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in keydb.conf to a larger value.");
|
||||
serverLog(LL_WARNING,"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value.");
|
||||
cancelReplicationHandshake(mi);
|
||||
}
|
||||
|
||||
/* Timed out master when we are an already connected replica? */
|
||||
if (mi->masterhost && mi->master && mi->repl_state == REPL_STATE_CONNECTED &&
|
||||
if (mi->masterhost && mi->repl_state == REPL_STATE_CONNECTED &&
|
||||
(time(NULL)-mi->master->lastinteraction) > g_pserver->repl_timeout)
|
||||
{
|
||||
serverLog(LL_WARNING,"MASTER timeout: no data nor PING received...");
|
||||
@@ -3266,8 +3173,6 @@ void replicationCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
propagateMasterStaleKeys();
|
||||
|
||||
/* Refresh the number of slaves with lag <= min-slaves-max-lag. */
|
||||
refreshGoodSlavesCount();
|
||||
replication_cron_loops++; /* Incremented with frequency 1 HZ. */
|
||||
@@ -3356,35 +3261,16 @@ public:
|
||||
return m_cnesting == 1;
|
||||
}
|
||||
|
||||
redisMaster *getMi(client *c)
|
||||
{
|
||||
if (m_mi == nullptr)
|
||||
m_mi = MasterInfoFromClient(c);
|
||||
return m_mi;
|
||||
}
|
||||
|
||||
int nesting() const { return m_cnesting; }
|
||||
|
||||
private:
|
||||
int m_cnesting = 0;
|
||||
bool m_fCancelled = false;
|
||||
redisMaster *m_mi = nullptr;
|
||||
};
|
||||
|
||||
static thread_local std::unique_ptr<ReplicaNestState> s_pstate;
|
||||
|
||||
bool FInReplicaReplay()
|
||||
{
|
||||
return s_pstate != nullptr && s_pstate->nesting() > 0;
|
||||
}
|
||||
|
||||
|
||||
static std::unordered_map<std::string, uint64_t> g_mapmvcc;
|
||||
|
||||
void replicaReplayCommand(client *c)
|
||||
{
|
||||
static thread_local ReplicaNestState *s_pstate = nullptr;
|
||||
if (s_pstate == nullptr)
|
||||
s_pstate = std::make_unique<ReplicaNestState>();
|
||||
s_pstate = new (MALLOC_LOCAL) ReplicaNestState;
|
||||
|
||||
// the replay command contains two arguments:
|
||||
// 1: The UUID of the source
|
||||
@@ -3406,10 +3292,9 @@ void replicaReplayCommand(client *c)
|
||||
return;
|
||||
}
|
||||
|
||||
std::string uuid;
|
||||
uuid.resize(UUID_BINARY_LEN);
|
||||
unsigned char uuid[UUID_BINARY_LEN];
|
||||
if (c->argv[1]->type != OBJ_STRING || sdslen((sds)ptrFromObj(c->argv[1])) != 36
|
||||
|| uuid_parse((sds)ptrFromObj(c->argv[1]), (unsigned char*)uuid.data()) != 0)
|
||||
|| uuid_parse((sds)ptrFromObj(c->argv[1]), uuid) != 0)
|
||||
{
|
||||
addReplyError(c, "Expected UUID arg1");
|
||||
s_pstate->Cancel();
|
||||
@@ -3434,18 +3319,7 @@ void replicaReplayCommand(client *c)
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t mvcc = 0;
|
||||
if (c->argc >= 5)
|
||||
{
|
||||
if (getUnsignedLongLongFromObject(c->argv[4], &mvcc) != C_OK)
|
||||
{
|
||||
addReplyError(c, "Invalid MVCC Timestamp");
|
||||
s_pstate->Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (FSameUuidNoNil((unsigned char*)uuid.data(), cserver.uuid))
|
||||
if (FSameUuidNoNil(uuid, cserver.uuid))
|
||||
{
|
||||
addReply(c, shared.ok);
|
||||
s_pstate->Cancel();
|
||||
@@ -3455,59 +3329,28 @@ void replicaReplayCommand(client *c)
|
||||
if (!s_pstate->FPush())
|
||||
return;
|
||||
|
||||
redisMaster *mi = s_pstate->getMi(c);
|
||||
client *cFake = mi->clientFake;
|
||||
if (mi->clientFakeNesting != s_pstate->nesting())
|
||||
cFake = nullptr;
|
||||
serverAssert(mi != nullptr);
|
||||
if (mvcc != 0 && g_mapmvcc[uuid] >= mvcc)
|
||||
{
|
||||
s_pstate->Cancel();
|
||||
s_pstate->Pop();
|
||||
return;
|
||||
}
|
||||
|
||||
// OK We've recieved a command lets execute
|
||||
client *current_clientSave = serverTL->current_client;
|
||||
if (cFake == nullptr)
|
||||
cFake = createClient(-1, c->iel);
|
||||
client *cFake = createClient(-1, c->iel);
|
||||
cFake->lock.lock();
|
||||
cFake->authenticated = c->authenticated;
|
||||
cFake->puser = c->puser;
|
||||
cFake->querybuf = sdscatsds(cFake->querybuf,(sds)ptrFromObj(c->argv[2]));
|
||||
selectDb(cFake, c->db->id);
|
||||
auto ccmdPrev = serverTL->commandsExecuted;
|
||||
cFake->flags |= CLIENT_MASTER | CLIENT_PREVENT_REPL_PROP;
|
||||
processInputBuffer(cFake, (CMD_CALL_FULL & (~CMD_CALL_PROPAGATE)));
|
||||
cFake->flags &= ~(CLIENT_MASTER | CLIENT_PREVENT_REPL_PROP);
|
||||
bool fExec = ccmdPrev != serverTL->commandsExecuted;
|
||||
cFake->lock.unlock();
|
||||
if (cFake->master_error)
|
||||
addReplyError(c, "Error in rreplay command, please check logs");
|
||||
if (fExec || cFake->flags & CLIENT_MULTI)
|
||||
if (fExec)
|
||||
{
|
||||
addReply(c, shared.ok);
|
||||
selectDb(c, cFake->db->id);
|
||||
if (mvcc > g_mapmvcc[uuid])
|
||||
g_mapmvcc[uuid] = mvcc;
|
||||
}
|
||||
else
|
||||
{
|
||||
serverLog(LL_WARNING, "Command didn't execute: %s", cFake->buf);
|
||||
addReplyError(c, "command did not execute");
|
||||
}
|
||||
serverAssert(sdslen(cFake->querybuf) == 0);
|
||||
if (cFake->flags & CLIENT_MULTI)
|
||||
{
|
||||
mi->clientFake = cFake;
|
||||
mi->clientFakeNesting = s_pstate->nesting();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mi->clientFake == cFake)
|
||||
mi->clientFake = nullptr;
|
||||
freeClient(cFake);
|
||||
}
|
||||
freeClient(cFake);
|
||||
serverTL->current_client = current_clientSave;
|
||||
|
||||
// call() will not propogate this for us, so we do so here
|
||||
@@ -3535,44 +3378,4 @@ void updateMasterAuth()
|
||||
if (cserver.default_masteruser)
|
||||
mi->masteruser = zstrdup(cserver.default_masteruser);
|
||||
}
|
||||
}
|
||||
|
||||
static void propagateMasterStaleKeys()
|
||||
{
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(g_pserver->masters, &li);
|
||||
robj *rgobj[2];
|
||||
|
||||
rgobj[0] = createEmbeddedStringObject("DEL", 3);
|
||||
|
||||
while ((ln = listNext(&li)) != nullptr)
|
||||
{
|
||||
redisMaster *mi = (redisMaster*)listNodeValue(ln);
|
||||
if (mi->staleKeyMap != nullptr)
|
||||
{
|
||||
if (mi->master != nullptr)
|
||||
{
|
||||
for (auto &pair : *mi->staleKeyMap)
|
||||
{
|
||||
if (pair.second.empty())
|
||||
continue;
|
||||
|
||||
client *replica = replicaFromMaster(mi);
|
||||
if (replica == nullptr)
|
||||
continue;
|
||||
|
||||
for (auto &spkey : pair.second)
|
||||
{
|
||||
rgobj[1] = spkey.get();
|
||||
replicationFeedSlave(replica, pair.first, rgobj, 2, false);
|
||||
}
|
||||
}
|
||||
delete mi->staleKeyMap;
|
||||
mi->staleKeyMap = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decrRefCount(rgobj[0]);
|
||||
}
|
||||
}
|
||||
@@ -53,18 +53,11 @@ static inline int sdsHdrSize(char type) {
|
||||
return sizeof(struct sdshdr32);
|
||||
case SDS_TYPE_64:
|
||||
return sizeof(struct sdshdr64);
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
return sizeof(struct sdshdrrefcount);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline char sdsReqType(ssize_t string_size) {
|
||||
if (string_size < 0){
|
||||
string_size = -string_size;
|
||||
if (string_size < 1<<16)
|
||||
return SDS_TYPE_REFCOUNTED;
|
||||
}
|
||||
static inline char sdsReqType(size_t string_size) {
|
||||
if (string_size < 1<<5)
|
||||
return SDS_TYPE_5;
|
||||
if (string_size < 1<<8)
|
||||
@@ -93,12 +86,10 @@ static inline char sdsReqType(ssize_t string_size) {
|
||||
* You can print the string with printf() as there is an implicit \0 at the
|
||||
* end of the string. However the string is binary safe and can contain
|
||||
* \0 characters in the middle, as the length is stored in the sds header. */
|
||||
sds sdsnewlen(const void *init, ssize_t initlen) {
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
void *sh;
|
||||
sds s;
|
||||
char type = sdsReqType(initlen);
|
||||
if (initlen < 0)
|
||||
initlen = -initlen;
|
||||
/* Empty strings are usually created in order to append. Use type 8
|
||||
* since type 5 is not good at this. */
|
||||
if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
|
||||
@@ -146,13 +137,6 @@ sds sdsnewlen(const void *init, ssize_t initlen) {
|
||||
*fp = type;
|
||||
break;
|
||||
}
|
||||
case SDS_TYPE_REFCOUNTED: {
|
||||
SDS_HDR_VAR_REFCOUNTED(s);
|
||||
sh->len = initlen;
|
||||
sh->refcount = 1;
|
||||
*fp = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (initlen && init)
|
||||
memcpy(s, init, initlen);
|
||||
@@ -177,25 +161,9 @@ sds sdsdup(const char *s) {
|
||||
return sdsnewlen(s, sdslen(s));
|
||||
}
|
||||
|
||||
sds sdsdupshared(const char *s) {
|
||||
unsigned char flags = s[-1];
|
||||
if ((flags & SDS_TYPE_MASK) != SDS_TYPE_REFCOUNTED)
|
||||
return sdsnewlen(s, -sdslen(s));
|
||||
SDS_HDR_VAR_REFCOUNTED(s);
|
||||
__atomic_fetch_add(&sh->refcount, 1, __ATOMIC_RELAXED);
|
||||
return (sds)s;
|
||||
}
|
||||
|
||||
/* Free an sds string. No operation is performed if 's' is NULL. */
|
||||
void sdsfree(const char *s) {
|
||||
if (s == NULL) return;
|
||||
unsigned char flags = s[-1];
|
||||
if ((flags & SDS_TYPE_MASK) == SDS_TYPE_REFCOUNTED)
|
||||
{
|
||||
SDS_HDR_VAR_REFCOUNTED(s);
|
||||
if (__atomic_fetch_sub(&sh->refcount, 1, __ATOMIC_RELAXED) > 1)
|
||||
return;
|
||||
}
|
||||
s_free((char*)s-sdsHdrSize(s[-1]));
|
||||
}
|
||||
|
||||
@@ -400,11 +368,6 @@ void sdsIncrLen(sds s, ssize_t incr) {
|
||||
len = (sh->len += incr);
|
||||
break;
|
||||
}
|
||||
case SDS_TYPE_REFCOUNTED: {
|
||||
SDS_HDR_VAR_REFCOUNTED(s);
|
||||
len = (sh->len += incr);
|
||||
break;
|
||||
}
|
||||
default: len = 0; /* Just to avoid compilation warnings. */
|
||||
}
|
||||
s[len] = '\0';
|
||||
@@ -824,7 +787,7 @@ void sdstoupper(sds s) {
|
||||
* If two strings share exactly the same prefix, but one of the two has
|
||||
* additional characters, the longer string is considered to be greater than
|
||||
* the smaller one. */
|
||||
int sdscmp(const char *s1, const char *s2) {
|
||||
int sdscmp(const sds s1, const sds s2) {
|
||||
size_t l1, l2, minlen;
|
||||
int cmp;
|
||||
|
||||
|
||||
@@ -91,27 +91,15 @@ struct __attribute__ ((__packed__)) sdshdr64 {
|
||||
#endif
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) sdshdrrefcount {
|
||||
uint64_t len; /* used */
|
||||
uint16_t refcount;
|
||||
unsigned char flags; /* 3 lsb of type, 5 unused bits */
|
||||
#ifndef __cplusplus
|
||||
char buf[];
|
||||
#endif
|
||||
};
|
||||
|
||||
#define SDS_TYPE_5 0
|
||||
#define SDS_TYPE_8 1
|
||||
#define SDS_TYPE_16 2
|
||||
#define SDS_TYPE_32 3
|
||||
#define SDS_TYPE_64 4
|
||||
#define SDS_TYPE_REFCOUNTED 5
|
||||
#define SDS_TYPE_MASK 7
|
||||
#define SDS_TYPE_BITS 3
|
||||
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)(((void*)((s)-(sizeof(struct sdshdr##T)))));
|
||||
#define SDS_HDR_VAR_REFCOUNTED(s) struct sdshdrrefcount *sh = (struct sdshdrrefcount *)(((void*)((s)-(sizeof(struct sdshdrrefcount)))));
|
||||
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
|
||||
#define SDS_HDR_REFCOUNTED(s) ((struct sdshdrrefcount *)((s)-(sizeof(struct sdshdrrefcount))))
|
||||
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
|
||||
|
||||
static inline size_t sdslen(const char *s) {
|
||||
@@ -133,8 +121,6 @@ static inline size_t sdslen(const char *s) {
|
||||
return SDS_HDR(32,s)->len;
|
||||
case SDS_TYPE_64:
|
||||
return SDS_HDR(64,s)->len;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
return SDS_HDR_REFCOUNTED(s)->len;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -162,9 +148,6 @@ static inline size_t sdsavail(const char * s) {
|
||||
SDS_HDR_VAR(64,s);
|
||||
return sh->alloc - sh->len;
|
||||
}
|
||||
case SDS_TYPE_REFCOUNTED: {
|
||||
return 0; // immutable
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -190,9 +173,6 @@ static inline void sdssetlen(sds s, size_t newlen) {
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len = newlen;
|
||||
break;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
SDS_HDR_REFCOUNTED(s)->len = newlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,9 +198,6 @@ static inline void sdsinclen(sds s, size_t inc) {
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len += inc;
|
||||
break;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
SDS_HDR_REFCOUNTED(s)->len += inc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,8 +215,6 @@ static inline size_t sdsalloc(const sds s) {
|
||||
return SDS_HDR(32,s)->alloc;
|
||||
case SDS_TYPE_64:
|
||||
return SDS_HDR(64,s)->alloc;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
return SDS_HDR_REFCOUNTED(s)->len;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -262,22 +237,13 @@ static inline void sdssetalloc(sds s, size_t newlen) {
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->alloc = newlen;
|
||||
break;
|
||||
case SDS_TYPE_REFCOUNTED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int sdsisshared(const char *s)
|
||||
{
|
||||
unsigned char flags = s[-1];
|
||||
return ((flags & SDS_TYPE_MASK) == SDS_TYPE_REFCOUNTED);
|
||||
}
|
||||
|
||||
sds sdsnewlen(const void *init, ssize_t initlen);
|
||||
sds sdsnewlen(const void *init, size_t initlen);
|
||||
sds sdsnew(const char *init);
|
||||
sds sdsempty(void);
|
||||
sds sdsdup(const char *s);
|
||||
sds sdsdupshared(const char *s);
|
||||
void sdsfree(const char *s);
|
||||
sds sdsgrowzero(sds s, size_t len);
|
||||
sds sdscatlen(sds s, const void *t, size_t len);
|
||||
@@ -299,7 +265,7 @@ sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, ssize_t start, ssize_t end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
int sdscmp(const char *s1, const char *s2);
|
||||
int sdscmp(const sds s1, const sds s2);
|
||||
sds *sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count);
|
||||
void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
@@ -332,120 +298,6 @@ int sdsTest(int argc, char *argv[]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
class sdsview
|
||||
{
|
||||
protected:
|
||||
sds m_str = nullptr;
|
||||
|
||||
sdsview() = default; // Not allowed to create a sdsview directly with a nullptr
|
||||
public:
|
||||
sdsview(sds str)
|
||||
: m_str(str)
|
||||
{}
|
||||
|
||||
sdsview(const char *str)
|
||||
: m_str((sds)str)
|
||||
{}
|
||||
|
||||
bool operator<(const sdsview &other) const
|
||||
{
|
||||
return sdscmp(m_str, other.m_str) < 0;
|
||||
}
|
||||
|
||||
bool operator==(const sdsview &other) const
|
||||
{
|
||||
return sdscmp(m_str, other.m_str) == 0;
|
||||
}
|
||||
|
||||
bool operator==(const char *other) const
|
||||
{
|
||||
return sdscmp(m_str, other) == 0;
|
||||
}
|
||||
|
||||
char operator[](size_t idx) const
|
||||
{
|
||||
return m_str[idx];
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return sdslen(m_str);
|
||||
}
|
||||
|
||||
const char *get() const { return m_str; }
|
||||
|
||||
explicit operator const char*() const { return m_str; }
|
||||
};
|
||||
|
||||
class sdsstring : public sdsview
|
||||
{
|
||||
public:
|
||||
sdsstring() = default;
|
||||
explicit sdsstring(sds str)
|
||||
: sdsview(str)
|
||||
{}
|
||||
|
||||
sdsstring(const sdsstring &other)
|
||||
: sdsview(sdsdup(other.m_str))
|
||||
{}
|
||||
|
||||
sdsstring(sdsstring &&other)
|
||||
: sdsview(other.m_str)
|
||||
{
|
||||
other.m_str = nullptr;
|
||||
}
|
||||
|
||||
sdsstring &operator=(const sdsstring &other)
|
||||
{
|
||||
sdsfree(m_str);
|
||||
m_str = sdsdup(other.m_str);
|
||||
return *this;
|
||||
}
|
||||
|
||||
sdsstring &operator=(sds other)
|
||||
{
|
||||
sdsfree(m_str);
|
||||
m_str = sdsdup(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~sdsstring()
|
||||
{
|
||||
sdsfree(m_str);
|
||||
}
|
||||
};
|
||||
|
||||
class sdsimmutablestring : public sdsstring
|
||||
{
|
||||
public:
|
||||
sdsimmutablestring() = default;
|
||||
explicit sdsimmutablestring(sds str)
|
||||
: sdsstring(str)
|
||||
{}
|
||||
|
||||
explicit sdsimmutablestring(const char *str)
|
||||
: sdsstring((sds)str)
|
||||
{}
|
||||
|
||||
sdsimmutablestring(const sdsimmutablestring &other)
|
||||
: sdsstring(sdsdupshared(other.m_str))
|
||||
{}
|
||||
|
||||
sdsimmutablestring(sdsimmutablestring &&other)
|
||||
: sdsstring(other.m_str)
|
||||
{
|
||||
other.m_str = nullptr;
|
||||
}
|
||||
|
||||
auto &operator=(const sdsimmutablestring &other)
|
||||
{
|
||||
sdsfree(m_str);
|
||||
m_str = sdsdupshared(other.m_str);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+36
-92
@@ -35,7 +35,6 @@
|
||||
#include "latency.h"
|
||||
#include "atomicvar.h"
|
||||
#include "storage.h"
|
||||
#include "cron.h"
|
||||
#include <thread>
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
@@ -62,11 +61,8 @@
|
||||
#include <uuid/uuid.h>
|
||||
#include <mutex>
|
||||
#include "aelocker.h"
|
||||
#include "motd.h"
|
||||
|
||||
int g_fTestMode = false;
|
||||
const char *motd_url = "http://api.keydb.dev/motd/motd_server.txt";
|
||||
const char *motd_cache_file = "/.keydb-server-motd";
|
||||
|
||||
/* Our shared "common" objects */
|
||||
|
||||
@@ -1027,11 +1023,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
{"rreplay",replicaReplayCommand,-3,
|
||||
"read-only fast noprop",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"keydb.cron",cronCommand,-5,
|
||||
"write use-memory",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
0,NULL,0,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@@ -1693,16 +1685,13 @@ void clientsCron(int iel) {
|
||||
/* The following functions do different service checks on the client.
|
||||
* The protocol is that they return non-zero if the client was
|
||||
* terminated. */
|
||||
if (clientsCronHandleTimeout(c,now)) continue; // Client free'd so don't release the lock
|
||||
if (clientsCronHandleTimeout(c,now)) goto LContinue;
|
||||
if (clientsCronResizeQueryBuffer(c)) goto LContinue;
|
||||
if (clientsCronTrackExpansiveClients(c)) goto LContinue;
|
||||
LContinue:
|
||||
fastlock_unlock(&c->lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free any pending clients */
|
||||
freeClientsInAsyncFreeQueue(iel);
|
||||
}
|
||||
|
||||
/* This function handles 'background' operations we are required to do
|
||||
@@ -1711,9 +1700,9 @@ void clientsCron(int iel) {
|
||||
void databasesCron(void) {
|
||||
/* Expire keys by random sampling. Not required for slaves
|
||||
* as master will synthesize DELs for us. */
|
||||
if (g_pserver->active_expire_enabled && (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica)) {
|
||||
if (g_pserver->active_expire_enabled && listLength(g_pserver->masters) == 0) {
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
|
||||
} else if (listLength(g_pserver->masters) && !g_pserver->fActiveReplica) {
|
||||
} else if (listLength(g_pserver->masters)) {
|
||||
expireSlaveKeys();
|
||||
}
|
||||
|
||||
@@ -1823,9 +1812,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
/* Update the time cache. */
|
||||
updateCachedTime();
|
||||
|
||||
/* Unpause clients if enough time has elapsed */
|
||||
unpauseClientsIfNecessary();
|
||||
|
||||
g_pserver->hz = g_pserver->config_hz;
|
||||
/* Adapt the g_pserver->hz value to the number of configured clients. If we have
|
||||
* many clients, we want to call serverCron() with an higher frequency. */
|
||||
@@ -1833,7 +1819,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
while (listLength(g_pserver->clients) / g_pserver->hz >
|
||||
MAX_CLIENTS_PER_CLOCK_TICK)
|
||||
{
|
||||
g_pserver->hz += g_pserver->hz; // *= 2
|
||||
g_pserver->hz *= 2;
|
||||
if (g_pserver->hz > CONFIG_MAX_HZ) {
|
||||
g_pserver->hz = CONFIG_MAX_HZ;
|
||||
break;
|
||||
@@ -1897,7 +1883,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
/* We received a SIGTERM, shutting down here in a safe way, as it is
|
||||
* not ok doing so inside the signal handler. */
|
||||
if (g_pserver->shutdown_asap) {
|
||||
if (prepareForShutdown(SHUTDOWN_NOFLAGS) == C_OK) throw ShutdownException();
|
||||
if (prepareForShutdown(SHUTDOWN_NOFLAGS) == C_OK) exit(0);
|
||||
serverLog(LL_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
|
||||
g_pserver->shutdown_asap = 0;
|
||||
}
|
||||
@@ -2033,6 +2019,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
flushAppendOnlyFile(0);
|
||||
}
|
||||
|
||||
/* Clear the paused clients flag if needed. */
|
||||
clientsArePaused(); /* Don't check return value, just use the side effect.*/
|
||||
|
||||
/* Replication cron function -- used to reconnect to master,
|
||||
* detect transfer failures, start background RDB transfers and so forth. */
|
||||
run_with_period(1000) replicationCron();
|
||||
@@ -2089,9 +2078,6 @@ int serverCronLite(struct aeEventLoop *eventLoop, long long id, void *clientData
|
||||
{
|
||||
processUnblockedClients(iel);
|
||||
}
|
||||
|
||||
/* Unpause clients if enough time has elapsed */
|
||||
unpauseClientsIfNecessary();
|
||||
|
||||
ProcessPendingAsyncWrites(); // A bug but leave for now, events should clean up after themselves
|
||||
clientsCron(iel);
|
||||
@@ -2113,7 +2099,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
|
||||
/* Run a fast expire cycle (the called function will return
|
||||
* ASAP if a fast cycle is not needed). */
|
||||
if (g_pserver->active_expire_enabled && (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica))
|
||||
if (g_pserver->active_expire_enabled && listLength(g_pserver->masters) == 0)
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);
|
||||
|
||||
/* Send all the slaves an ACK request if at least one client blocked
|
||||
@@ -2327,7 +2313,6 @@ void initMasterInfo(redisMaster *master)
|
||||
|
||||
master->repl_state = REPL_STATE_NONE;
|
||||
master->repl_down_since = 0; /* Never connected, repl is down since EVER. */
|
||||
master->mvccLastSync = 0;
|
||||
}
|
||||
|
||||
void initServerConfig(void) {
|
||||
@@ -2495,7 +2480,7 @@ void initServerConfig(void) {
|
||||
|
||||
/* Command table -- we initiialize it here as it is part of the
|
||||
* initial configuration, since command names may be changed via
|
||||
* keydb.conf using the rename-command directive. */
|
||||
* redis.conf using the rename-command directive. */
|
||||
g_pserver->commands = dictCreate(&commandTableDictType,NULL);
|
||||
g_pserver->orig_commands = dictCreate(&commandTableDictType,NULL);
|
||||
populateCommandTable();
|
||||
@@ -2531,7 +2516,7 @@ void initServerConfig(void) {
|
||||
/* By default we want scripts to be always replicated by effects
|
||||
* (single commands executed by the script), and not by sending the
|
||||
* script to the replica / AOF. This is the new way starting from
|
||||
* Redis 5. However it is possible to revert it via keydb.conf. */
|
||||
* Redis 5. However it is possible to revert it via redis.conf. */
|
||||
g_pserver->lua_always_replicate_commands = 1;
|
||||
|
||||
/* Multithreading */
|
||||
@@ -2886,8 +2871,6 @@ static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
|
||||
pvar->cclients = 0;
|
||||
pvar->el = aeCreateEventLoop(g_pserver->maxclients+CONFIG_FDSET_INCR);
|
||||
pvar->current_client = nullptr;
|
||||
pvar->clients_paused = 0;
|
||||
pvar->fRetrySetAofEvent = false;
|
||||
if (pvar->el == NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed creating the event loop. Error message: '%s'",
|
||||
@@ -2895,7 +2878,7 @@ static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fastlock_init(&pvar->lockPendingWrite, "lockPendingWrite");
|
||||
fastlock_init(&pvar->lockPendingWrite);
|
||||
|
||||
if (!fMain)
|
||||
{
|
||||
@@ -2942,6 +2925,8 @@ void initServer(void) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
setupSignalHandlers();
|
||||
|
||||
fastlock_init(&g_pserver->flock);
|
||||
|
||||
g_pserver->db = (redisDb*)zmalloc(sizeof(redisDb)*cserver.dbnum, MALLOC_LOCAL);
|
||||
|
||||
/* Create the Redis databases, and initialize other internal state. */
|
||||
@@ -2984,6 +2969,7 @@ void initServer(void) {
|
||||
g_pserver->ready_keys = listCreate();
|
||||
g_pserver->clients_waiting_acks = listCreate();
|
||||
g_pserver->get_ack_from_slaves = 0;
|
||||
g_pserver->clients_paused = 0;
|
||||
cserver.system_memory_size = zmalloc_get_memory_size();
|
||||
|
||||
createSharedObjects();
|
||||
@@ -3148,7 +3134,7 @@ void populateCommandTable(void) {
|
||||
c->id = ACLGetCommandID(c->name); /* Assign the ID used for ACL. */
|
||||
retval1 = dictAdd(g_pserver->commands, sdsnew(c->name), c);
|
||||
/* Populate an additional dictionary that will be unaffected
|
||||
* by rename-command statements in keydb.conf. */
|
||||
* by rename-command statements in redis.conf. */
|
||||
retval2 = dictAdd(g_pserver->orig_commands, sdsnew(c->name), c);
|
||||
serverAssert(retval1 == DICT_OK && retval2 == DICT_OK);
|
||||
}
|
||||
@@ -3223,7 +3209,7 @@ struct redisCommand *lookupCommandByCString(const char *s) {
|
||||
|
||||
/* Lookup the command in the current table, if not found also check in
|
||||
* the original table containing the original command names unaffected by
|
||||
* keydb.conf rename-command statement.
|
||||
* redis.conf rename-command statement.
|
||||
*
|
||||
* This is used by functions rewriting the argument vector such as
|
||||
* rewriteClientCommandVector() in order to set client->cmd pointer
|
||||
@@ -3720,6 +3706,7 @@ int processCommand(client *c, int callFlags) {
|
||||
queueMultiCommand(c);
|
||||
addReply(c,shared.queued);
|
||||
} else {
|
||||
std::unique_lock<decltype(c->db->lock)> ulock(c->db->lock);
|
||||
call(c,callFlags);
|
||||
c->woff = g_pserver->master_repl_offset;
|
||||
if (listLength(g_pserver->ready_keys))
|
||||
@@ -3815,17 +3802,8 @@ int prepareForShutdown(int flags) {
|
||||
|
||||
/* Close the listening sockets. Apparently this allows faster restarts. */
|
||||
closeListeningSockets(1);
|
||||
|
||||
for (int iel = 0; iel < cserver.cthreads; ++iel)
|
||||
{
|
||||
aePostFunction(g_pserver->rgthreadvar[iel].el, [iel]{
|
||||
g_pserver->rgthreadvar[iel].el->stop = 1;
|
||||
});
|
||||
}
|
||||
|
||||
serverLog(LL_WARNING,"%s is now ready to exit, bye bye...",
|
||||
g_pserver->sentinel_mode ? "Sentinel" : "KeyDB");
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -4113,7 +4091,7 @@ sds genRedisInfoString(const char *section) {
|
||||
g_pserver->port,
|
||||
(intmax_t)uptime,
|
||||
(intmax_t)(uptime/(3600*24)),
|
||||
g_pserver->hz.load(),
|
||||
g_pserver->hz,
|
||||
g_pserver->config_hz,
|
||||
(unsigned long) lruclock,
|
||||
cserver.executable ? cserver.executable : "",
|
||||
@@ -4702,7 +4680,7 @@ void daemonize(void) {
|
||||
}
|
||||
|
||||
void version(void) {
|
||||
printf("KeyDB server v=%s sha=%s:%d malloc=%s bits=%d build=%llx\n",
|
||||
printf("Redis server v=%s sha=%s:%d malloc=%s bits=%d build=%llx\n",
|
||||
KEYDB_REAL_VERSION,
|
||||
redisGitSHA1(),
|
||||
atoi(redisGitDirty()) > 0,
|
||||
@@ -4713,7 +4691,7 @@ void version(void) {
|
||||
}
|
||||
|
||||
void usage(void) {
|
||||
fprintf(stderr,"Usage: ./keydb-server [/path/to/keydb.conf] [options]\n");
|
||||
fprintf(stderr,"Usage: ./keydb-server [/path/to/redis.conf] [options]\n");
|
||||
fprintf(stderr," ./keydb-server - (read config from stdin)\n");
|
||||
fprintf(stderr," ./keydb-server -v or --version\n");
|
||||
fprintf(stderr," ./keydb-server -h or --help\n");
|
||||
@@ -4723,7 +4701,7 @@ void usage(void) {
|
||||
fprintf(stderr," ./keydb-server /etc/redis/6379.conf\n");
|
||||
fprintf(stderr," ./keydb-server --port 7777\n");
|
||||
fprintf(stderr," ./keydb-server --port 7777 --replicaof 127.0.0.1 8888\n");
|
||||
fprintf(stderr," ./keydb-server /etc/mykeydb.conf --loglevel verbose\n\n");
|
||||
fprintf(stderr," ./keydb-server /etc/myredis.conf --loglevel verbose\n\n");
|
||||
fprintf(stderr,"Sentinel mode:\n");
|
||||
fprintf(stderr," ./keydb-server /etc/sentinel.conf --sentinel\n");
|
||||
exit(1);
|
||||
@@ -4740,7 +4718,7 @@ void redisAsciiArt(void) {
|
||||
|
||||
/* Show the ASCII logo if: log file is stdout AND stdout is a
|
||||
* tty AND syslog logging is disabled. Also show logo if the user
|
||||
* forced us to do so via keydb.conf. */
|
||||
* forced us to do so via redis.conf. */
|
||||
int show_logo = ((!g_pserver->syslog_enabled &&
|
||||
g_pserver->logfile[0] == '\0' &&
|
||||
isatty(fileno(stdout))) ||
|
||||
@@ -4752,18 +4730,14 @@ void redisAsciiArt(void) {
|
||||
mode, g_pserver->port
|
||||
);
|
||||
} else {
|
||||
sds motd = fetchMOTD(true);
|
||||
snprintf(buf,1024*16,ascii_logo,
|
||||
KEYDB_REAL_VERSION,
|
||||
redisGitSHA1(),
|
||||
strtol(redisGitDirty(),NULL,10) > 0,
|
||||
(sizeof(long) == 8) ? "64" : "32",
|
||||
mode, g_pserver->port,
|
||||
(long) getpid(),
|
||||
motd ? motd : ""
|
||||
(long) getpid()
|
||||
);
|
||||
if (motd)
|
||||
sdsfree(motd);
|
||||
serverLogRaw(LL_NOTICE|LL_RAW,buf);
|
||||
}
|
||||
zfree(buf);
|
||||
@@ -5049,34 +5023,11 @@ void *workerThreadMain(void *parg)
|
||||
aeEventLoop *el = g_pserver->rgthreadvar[iel].el;
|
||||
aeSetBeforeSleepProc(el, isMainThread ? beforeSleep : beforeSleepLite, isMainThread ? 0 : AE_SLEEP_THREADSAFE);
|
||||
aeSetAfterSleepProc(el, afterSleep, AE_SLEEP_THREADSAFE);
|
||||
try
|
||||
{
|
||||
aeMain(el);
|
||||
}
|
||||
catch (ShutdownException)
|
||||
{
|
||||
}
|
||||
serverAssert(!GlobalLocksAcquired());
|
||||
aeMain(el);
|
||||
aeDeleteEventLoop(el);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void validateConfiguration()
|
||||
{
|
||||
if (cserver.cthreads > (int)std::thread::hardware_concurrency()) {
|
||||
serverLog(LL_WARNING, "WARNING: server-threads is greater than this machine's core count. Truncating to %u threads", std::thread::hardware_concurrency());
|
||||
cserver.cthreads = (int)std::thread::hardware_concurrency();
|
||||
cserver.cthreads = std::max(cserver.cthreads, 1); // in case of any weird sign overflows
|
||||
}
|
||||
|
||||
if (g_pserver->enable_multimaster && !g_pserver->fActiveReplica) {
|
||||
serverLog(LL_WARNING, "ERROR: Multi Master requires active replication to be enabled.");
|
||||
serverLog(LL_WARNING, "\tKeyDB will now exit. Please update your configuration file.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct timeval tv;
|
||||
int j;
|
||||
@@ -5128,6 +5079,10 @@ int main(int argc, char **argv) {
|
||||
dictSetHashFunctionSeed((uint8_t*)hashseed);
|
||||
g_pserver->sentinel_mode = checkForSentinelMode(argc,argv);
|
||||
initServerConfig();
|
||||
for (int iel = 0; iel < MAX_EVENT_LOOPS; ++iel)
|
||||
{
|
||||
initServerThread(g_pserver->rgthreadvar+iel, iel == IDX_EVENT_LOOP_MAIN);
|
||||
}
|
||||
serverTL = &g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN];
|
||||
aeAcquireLock(); // We own the lock on boot
|
||||
|
||||
@@ -5224,15 +5179,6 @@ int main(int argc, char **argv) {
|
||||
sdsfree(options);
|
||||
}
|
||||
|
||||
if (cserver.fUsePro) {
|
||||
const char *keydb_pro_dir = getenv("KEYDB_PRO_DIRECTORY");
|
||||
sds path = sdsnew(keydb_pro_dir);
|
||||
path = sdscat(path, "keydb-pro-server");
|
||||
execv(path, argv);
|
||||
perror("Failed launch the pro binary");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
serverLog(LL_WARNING, "oO0OoO0OoO0Oo KeyDB is starting oO0OoO0OoO0Oo");
|
||||
serverLog(LL_WARNING,
|
||||
"KeyDB version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started",
|
||||
@@ -5248,16 +5194,16 @@ int main(int argc, char **argv) {
|
||||
serverLog(LL_WARNING, "Configuration loaded");
|
||||
}
|
||||
|
||||
validateConfiguration();
|
||||
if (cserver.cthreads > (int)std::thread::hardware_concurrency()) {
|
||||
serverLog(LL_WARNING, "WARNING: server-threads is greater than this machine's core count. Truncating to %u threads", std::thread::hardware_concurrency());
|
||||
cserver.cthreads = (int)std::thread::hardware_concurrency();
|
||||
cserver.cthreads = std::max(cserver.cthreads, 1); // in case of any weird sign overflows
|
||||
}
|
||||
|
||||
cserver.supervised = redisIsSupervised(cserver.supervised_mode);
|
||||
int background = cserver.daemonize && !cserver.supervised;
|
||||
if (background) daemonize();
|
||||
|
||||
for (int iel = 0; iel < MAX_EVENT_LOOPS; ++iel)
|
||||
{
|
||||
initServerThread(g_pserver->rgthreadvar+iel, iel == IDX_EVENT_LOOP_MAIN);
|
||||
}
|
||||
initServer();
|
||||
initNetworking(cserver.cthreads > 1 /* fReusePort */);
|
||||
|
||||
@@ -5358,9 +5304,7 @@ int main(int argc, char **argv) {
|
||||
/* The main thread sleeps until all the workers are done.
|
||||
this is so that all worker threads are orthogonal in their startup/shutdown */
|
||||
void *pvRet;
|
||||
for (int iel = 0; iel < cserver.cthreads; ++iel)
|
||||
pthread_join(rgthread[iel], &pvRet);
|
||||
|
||||
pthread_join(rgthread[IDX_EVENT_LOOP_MAIN], &pvRet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+14
-116
@@ -54,8 +54,6 @@
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
@@ -146,87 +144,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void decrRefCount(robj_roptr o);
|
||||
void incrRefCount(robj_roptr o);
|
||||
class robj_sharedptr
|
||||
{
|
||||
redisObject *m_ptr;
|
||||
|
||||
public:
|
||||
robj_sharedptr()
|
||||
: m_ptr(nullptr)
|
||||
{}
|
||||
robj_sharedptr(redisObject *ptr)
|
||||
: m_ptr(ptr)
|
||||
{
|
||||
incrRefCount(ptr);
|
||||
}
|
||||
~robj_sharedptr()
|
||||
{
|
||||
if (m_ptr)
|
||||
decrRefCount(m_ptr);
|
||||
}
|
||||
robj_sharedptr(const robj_sharedptr& other)
|
||||
{
|
||||
m_ptr = other.m_ptr;
|
||||
incrRefCount(m_ptr);
|
||||
}
|
||||
|
||||
robj_sharedptr(robj_sharedptr&& other)
|
||||
{
|
||||
m_ptr = other.m_ptr;
|
||||
other.m_ptr = nullptr;
|
||||
}
|
||||
|
||||
robj_sharedptr &operator=(const robj_sharedptr& other)
|
||||
{
|
||||
if (m_ptr)
|
||||
decrRefCount(m_ptr);
|
||||
m_ptr = other.m_ptr;
|
||||
incrRefCount(m_ptr);
|
||||
return *this;
|
||||
}
|
||||
robj_sharedptr &operator=(redisObject *ptr)
|
||||
{
|
||||
if (m_ptr)
|
||||
decrRefCount(m_ptr);
|
||||
m_ptr = ptr;
|
||||
incrRefCount(m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const robj_sharedptr &other) const
|
||||
{
|
||||
return m_ptr == other.m_ptr;
|
||||
}
|
||||
|
||||
bool operator!=(const robj_sharedptr &other) const
|
||||
{
|
||||
return m_ptr != other.m_ptr;
|
||||
}
|
||||
|
||||
redisObject* operator->() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return !m_ptr;
|
||||
}
|
||||
|
||||
operator bool() const{
|
||||
return !!m_ptr;
|
||||
}
|
||||
|
||||
operator redisObject *()
|
||||
{
|
||||
return (redisObject*)m_ptr;
|
||||
}
|
||||
|
||||
redisObject *get() { return m_ptr; }
|
||||
};
|
||||
|
||||
/* Error codes */
|
||||
#define C_OK 0
|
||||
#define C_ERR -1
|
||||
@@ -265,7 +182,7 @@ public:
|
||||
#define CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT (60*60) /* 1 hour */
|
||||
#define CONFIG_REPL_BACKLOG_MIN_SIZE (1024*16) /* 16k */
|
||||
#define CONFIG_BGSAVE_RETRY_DELAY 5 /* Wait a few secs before trying again. */
|
||||
#define CONFIG_DEFAULT_PID_FILE "/var/run/keydb.pid"
|
||||
#define CONFIG_DEFAULT_PID_FILE "/var/run/redis.pid"
|
||||
#define CONFIG_DEFAULT_SYSLOG_IDENT "redis"
|
||||
#define CONFIG_DEFAULT_CLUSTER_CONFIG_FILE "nodes.conf"
|
||||
#define CONFIG_DEFAULT_CLUSTER_ANNOUNCE_IP NULL /* Auto detect. */
|
||||
@@ -516,7 +433,6 @@ public:
|
||||
#define SLAVE_CAPA_NONE 0
|
||||
#define SLAVE_CAPA_EOF (1<<0) /* Can parse the RDB EOF streaming format. */
|
||||
#define SLAVE_CAPA_PSYNC2 (1<<1) /* Supports PSYNC2 protocol. */
|
||||
#define SLAVE_CAPA_ACTIVE_EXPIRE (1<<2) /* Will the slave perform its own expirations? (Don't send delete) */
|
||||
|
||||
/* Synchronous read timeout - replica side */
|
||||
#define CONFIG_REPL_SYNCIO_TIMEOUT 5
|
||||
@@ -684,8 +600,6 @@ public:
|
||||
* encoding version. */
|
||||
#define OBJ_MODULE 5 /* Module object. */
|
||||
#define OBJ_STREAM 6 /* Stream object. */
|
||||
#define OBJ_CRON 7 /* CRON job */
|
||||
|
||||
|
||||
/* Extract encver / signature from a module type ID. */
|
||||
#define REDISMODULE_TYPE_ENCVER_BITS 10
|
||||
@@ -1128,13 +1042,10 @@ typedef struct 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. */
|
||||
struct redisDb {
|
||||
typedef struct redisDb {
|
||||
redisDb()
|
||||
: expireitr(nullptr)
|
||||
{};
|
||||
|
||||
~redisDb();
|
||||
|
||||
dict *pdict; /* The keyspace for this DB */
|
||||
expireset *setexpire;
|
||||
expireset::setiter expireitr;
|
||||
@@ -1146,7 +1057,9 @@ struct redisDb {
|
||||
long long last_expire_set; /* when the last expire was set */
|
||||
double avg_ttl; /* Average TTL, just for stats */
|
||||
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
|
||||
};
|
||||
|
||||
fastlock lock;
|
||||
} redisDb;
|
||||
|
||||
/* Client MULTI/EXEC state */
|
||||
typedef struct multiCmd {
|
||||
@@ -1338,7 +1251,6 @@ typedef struct client {
|
||||
|
||||
int iel; /* the event loop index we're registered with */
|
||||
struct fastlock lock;
|
||||
int master_error;
|
||||
} client;
|
||||
|
||||
struct saveparam {
|
||||
@@ -1480,11 +1392,9 @@ typedef struct rdbSaveInfo {
|
||||
char repl_id[CONFIG_RUN_ID_SIZE+1]; /* Replication ID. */
|
||||
long long repl_offset; /* Replication offset. */
|
||||
int fForceSetKey;
|
||||
uint64_t mvccMinThreshold;
|
||||
struct redisMaster *mi;
|
||||
} rdbSaveInfo;
|
||||
|
||||
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1, TRUE, 0, nullptr}
|
||||
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1, TRUE}
|
||||
|
||||
struct malloc_stats {
|
||||
size_t zmalloc_used;
|
||||
@@ -1518,7 +1428,6 @@ struct redisServerThreadVars {
|
||||
aeEventLoop *el;
|
||||
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
|
||||
int ipfd_count; /* Used slots in ipfd[] */
|
||||
int clients_paused; /* True if clients are currently paused */
|
||||
std::vector<client*> clients_pending_write; /* There is to write or install handler. */
|
||||
list *unblocked_clients; /* list of clients to unblock before next loop NOT THREADSAFE */
|
||||
list *clients_pending_asyncwrite;
|
||||
@@ -1528,10 +1437,9 @@ struct redisServerThreadVars {
|
||||
client blocked on a module command needs
|
||||
to be processed. */
|
||||
client *lua_client = nullptr; /* The "fake client" to query Redis from Lua */
|
||||
struct fastlock lockPendingWrite { "thread pending write" };
|
||||
struct fastlock lockPendingWrite;
|
||||
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
|
||||
long unsigned commandsExecuted = 0;
|
||||
bool fRetrySetAofEvent = false;
|
||||
};
|
||||
|
||||
struct redisMaster {
|
||||
@@ -1541,8 +1449,6 @@ struct redisMaster {
|
||||
int masterport; /* Port of master */
|
||||
client *cached_master; /* Cached master to be reused for PSYNC. */
|
||||
client *master;
|
||||
client *clientFake;
|
||||
int clientFakeNesting;
|
||||
/* The following two fields is where we store master PSYNC replid/offset
|
||||
* while the PSYNC is in progress. At the end we'll copy the fields into
|
||||
* the server->master client structure. */
|
||||
@@ -1561,9 +1467,6 @@ struct redisMaster {
|
||||
|
||||
unsigned char master_uuid[UUID_BINARY_LEN]; /* Used during sync with master, this is our master's UUID */
|
||||
/* After we've connected with our master use the UUID in g_pserver->master */
|
||||
uint64_t mvccLastSync;
|
||||
/* During a handshake the server may have stale keys, we track these here to share once a reciprocal connection is made */
|
||||
std::map<int, std::vector<robj_sharedptr>> *staleKeyMap;
|
||||
};
|
||||
|
||||
// Const vars are not changed after worker threads are launched
|
||||
@@ -1588,7 +1491,7 @@ struct redisServerConst {
|
||||
/* Configuration */
|
||||
char *default_masteruser; /* AUTH with this user and masterauth with master */
|
||||
char *default_masterauth; /* AUTH with this password with master */
|
||||
int verbosity; /* Loglevel in keydb.conf */
|
||||
int verbosity; /* Loglevel in redis.conf */
|
||||
int maxidletime; /* Client timeout in seconds */
|
||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||
int active_defrag_enabled;
|
||||
@@ -1609,8 +1512,6 @@ struct redisServerConst {
|
||||
size_t system_memory_size; /* Total memory in system as reported by OS */
|
||||
|
||||
unsigned char uuid[UUID_BINARY_LEN]; /* This server's UUID - populated on boot */
|
||||
bool fUsePro = false;
|
||||
int thread_min_client_threshold = 50;
|
||||
};
|
||||
|
||||
struct redisServer {
|
||||
@@ -1619,7 +1520,7 @@ struct redisServer {
|
||||
int config_hz; /* Configured HZ value. May be different than
|
||||
the actual 'hz' field value if dynamic-hz
|
||||
is enabled. */
|
||||
std::atomic<int> hz; /* serverCron() calls frequency in hertz */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table */
|
||||
dict *orig_commands; /* Command table before command renaming. */
|
||||
@@ -1654,6 +1555,7 @@ struct redisServer {
|
||||
list *clients_to_close; /* Clients to close asynchronously */
|
||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||
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 */
|
||||
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
|
||||
std::atomic<uint64_t> next_client_id; /* Next client unique ID. Incremental. */
|
||||
@@ -1744,7 +1646,6 @@ struct redisServer {
|
||||
int aof_stop_sending_diff; /* If true stop sending accumulated diffs
|
||||
to child process. */
|
||||
sds aof_child_diff; /* AOF diff accumulator child side. */
|
||||
int aof_rewrite_pending = 0; /* is a call to aofChildWriteDiffData already queued? */
|
||||
/* RDB persistence */
|
||||
long long dirty; /* Changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||
@@ -1842,7 +1743,7 @@ struct redisServer {
|
||||
int sort_alpha;
|
||||
int sort_bypattern;
|
||||
int sort_store;
|
||||
/* Zip structure config, see keydb.conf for more information */
|
||||
/* Zip structure config, see redis.conf for more information */
|
||||
size_t hash_max_ziplist_entries;
|
||||
size_t hash_max_ziplist_value;
|
||||
size_t set_max_intset_entries;
|
||||
@@ -1918,6 +1819,8 @@ struct redisServer {
|
||||
|
||||
int fActiveReplica; /* Can this replica also be a master? */
|
||||
|
||||
struct fastlock flock;
|
||||
|
||||
// Format:
|
||||
// Lower 20 bits: a counter incrementing for each command executed in the same millisecond
|
||||
// Upper 44 bits: mstime (least significant 44-bits) enough for ~500 years before rollover from date of addition
|
||||
@@ -2143,7 +2046,6 @@ void disconnectSlavesExcept(unsigned char *uuid);
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListen);
|
||||
void pauseClients(mstime_t duration);
|
||||
int clientsArePaused(void);
|
||||
void unpauseClientsIfNecessary();
|
||||
int processEventsWhileBlocked(int iel);
|
||||
int handleClientsWithPendingWrites(int iel);
|
||||
int clientHasPendingReplies(client *c);
|
||||
@@ -2256,7 +2158,6 @@ int getLongLongFromObjectOrReply(client *c, robj *o, long long *target, const ch
|
||||
int getDoubleFromObjectOrReply(client *c, robj *o, double *target, const char *msg);
|
||||
int getDoubleFromObject(const robj *o, double *target);
|
||||
int getLongLongFromObject(robj *o, long long *target);
|
||||
int getUnsignedLongLongFromObject(robj *o, uint64_t *target);
|
||||
int getLongDoubleFromObject(robj *o, long double *target);
|
||||
int getLongDoubleFromObjectOrReply(client *c, robj *o, long double *target, const char *msg);
|
||||
const char *strEncoding(int encoding);
|
||||
@@ -2898,7 +2799,7 @@ void xorDigest(unsigned char *digest, const void *ptr, size_t len);
|
||||
int populateCommandTableParseFlags(struct redisCommand *c, const char *strflags);
|
||||
|
||||
int moduleGILAcquiredByModule(void);
|
||||
extern int g_fInCrash;
|
||||
extern bool g_fInCrash;
|
||||
static inline int GlobalLocksAcquired(void) // Used in asserts to verify all global locks are correctly acquired for a server-thread to operate
|
||||
{
|
||||
return aeThreadOwnsLock() || moduleGILAcquiredByModule() || g_fInCrash;
|
||||
@@ -2924,9 +2825,6 @@ inline int FCorrectThread(client *c)
|
||||
}
|
||||
#define AssertCorrectThread(c) serverAssert(FCorrectThread(c))
|
||||
|
||||
class ShutdownException
|
||||
{};
|
||||
|
||||
#define redisDebug(fmt, ...) \
|
||||
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define redisDebugMark() \
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
#define KEYDB_REAL_VERSION "0.0.0"
|
||||
#define KEYDB_REAL_VERSION "5.1.0"
|
||||
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
|
||||
|
||||
@@ -14,8 +14,6 @@ proc main {} {
|
||||
spawn_instance redis $::redis_base_port $::instances_count {
|
||||
"cluster-enabled yes"
|
||||
"appendonly yes"
|
||||
"testmode yes"
|
||||
"server-threads 3"
|
||||
}
|
||||
run_tests
|
||||
cleanup
|
||||
|
||||
@@ -6,7 +6,7 @@ if {$system_name eq {linux} || $system_name eq {darwin}} {
|
||||
test "Server is able to generate a stack trace on selected systems" {
|
||||
r config set watchdog-period 200
|
||||
r debug sleep 1
|
||||
set pattern "*watchdogSignalHandler*"
|
||||
set pattern "*debugCommand*"
|
||||
set retry 10
|
||||
while {$retry} {
|
||||
set result [exec tail -100 < [srv 0 stdout]]
|
||||
|
||||
@@ -9,7 +9,6 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set master_pid [s process_id]
|
||||
|
||||
# Use a short replication timeout on the slave, so that if there
|
||||
# are no bugs the timeout is triggered in a reasonable amount
|
||||
@@ -59,24 +58,6 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {Active replicas propogate transaction} {
|
||||
$master set testkey 0
|
||||
$master multi
|
||||
$master incr testkey
|
||||
$master incr testkey
|
||||
after 5000
|
||||
$master get testkey
|
||||
$master exec
|
||||
assert_equal 2 [$master get testkey]
|
||||
after 500
|
||||
wait_for_condition 50 500 {
|
||||
[string match "2" [$slave get testkey]]
|
||||
} else {
|
||||
fail "Transaction failed to replicate"
|
||||
}
|
||||
$master flushall
|
||||
}
|
||||
|
||||
test {Active replicas WAIT} {
|
||||
# Test that wait succeeds since replicas should be syncronized
|
||||
$master set testkey foo
|
||||
@@ -113,26 +94,6 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
|
||||
assert_equal {0} [$slave del testkey1]
|
||||
}
|
||||
|
||||
test {Active replica expire propogates when source is down} {
|
||||
$slave flushall
|
||||
$slave set testkey2 foo
|
||||
$slave set testkey1 foo
|
||||
wait_for_condition 50 1000 {
|
||||
[string match *foo* [$master get testkey1]]
|
||||
} else {
|
||||
fail "Replication failed to propogate"
|
||||
}
|
||||
$slave expire testkey1 2
|
||||
assert_equal {1} [$slave wait 1 500] { "value should propogate
|
||||
within 0.5 seconds" }
|
||||
exec kill -SIGSTOP $slave_pid
|
||||
after 3000
|
||||
# Ensure testkey1 is gone. Note, we can't do this directly as the normal commands lie to us
|
||||
# about what is actually in the dict. The only way to know is with a count from info
|
||||
assert_equal {1} [expr [string first {keys=1} [$master info keyspace]] >= 0] {"slave expired"}
|
||||
}
|
||||
exec kill -SIGCONT $slave_pid
|
||||
|
||||
test {Active replica different databases} {
|
||||
$master select 3
|
||||
$master set testkey abcd
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
foreach topology {mesh ring} {
|
||||
start_server {tags {"multi-master"} overrides {hz 500 active-replica yes multi-master yes}} {
|
||||
start_server {overrides {hz 500 active-replica yes multi-master yes}} {
|
||||
start_server {overrides {hz 500 active-replica yes multi-master yes}} {
|
||||
start_server {overrides {hz 500 active-replica yes multi-master yes}} {
|
||||
|
||||
for {set j 0} {$j < 4} {incr j} {
|
||||
set R($j) [srv [expr 0-$j] client]
|
||||
set R_host($j) [srv [expr 0-$j] host]
|
||||
set R_port($j) [srv [expr 0-$j] port]
|
||||
}
|
||||
|
||||
# Initialize as mesh
|
||||
if [string equal $topology "mesh"] {
|
||||
for {set j 0} {$j < 4} {incr j} {
|
||||
for {set k 0} {$k < 4} {incr k} {
|
||||
if $j!=$k {
|
||||
$R($j) replicaof $R_host($k) $R_port($k)
|
||||
after 100
|
||||
}
|
||||
}
|
||||
}}
|
||||
#Else Ring
|
||||
if [string equal $topology "ring"] {
|
||||
$R(0) replicaof $R_host(3) $R_port(3)
|
||||
after 100
|
||||
$R(1) replicaof $R_host(0) $R_port(0)
|
||||
after 100
|
||||
$R(2) replicaof $R_host(1) $R_port(1)
|
||||
after 100
|
||||
$R(3) replicaof $R_host(2) $R_port(2)
|
||||
}
|
||||
|
||||
after 2000
|
||||
|
||||
test "$topology replicates to all nodes" {
|
||||
$R(0) set testkey foo
|
||||
after 500
|
||||
assert_equal foo [$R(1) get testkey] "replicates to 1"
|
||||
assert_equal foo [$R(2) get testkey] "replicates to 2"
|
||||
}
|
||||
|
||||
test "$topology replicates only once" {
|
||||
$R(0) set testkey 1
|
||||
after 500
|
||||
$R(1) incr testkey
|
||||
after 500
|
||||
$R(2) incr testkey
|
||||
after 500
|
||||
assert_equal 3 [$R(0) get testkey]
|
||||
assert_equal 3 [$R(1) get testkey]
|
||||
assert_equal 3 [$R(2) get testkey]
|
||||
assert_equal 3 [$R(3) get testkey]
|
||||
}
|
||||
|
||||
test "$topology transaction replicates only once" {
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
$R(0) set testkey 1
|
||||
$R(0) multi
|
||||
$R(0) incr testkey
|
||||
$R(0) incr testkey
|
||||
$R(0) exec
|
||||
after 1
|
||||
assert_equal 3 [$R(0) get testkey] "node 0"
|
||||
assert_equal 3 [$R(1) get testkey] "node 1"
|
||||
assert_equal 3 [$R(2) get testkey] "node 2"
|
||||
assert_equal 3 [$R(3) get testkey] "node 3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,5 +309,3 @@ start_server {tags {"repl"}} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@ set ::all_tests {
|
||||
unit/aofrw
|
||||
unit/acl
|
||||
unit/rreplay
|
||||
unit/cron
|
||||
unit/replication
|
||||
integration/block-repl
|
||||
integration/replication
|
||||
integration/replication-2
|
||||
@@ -45,7 +43,6 @@ set ::all_tests {
|
||||
integration/replication-4
|
||||
integration/replication-psync
|
||||
integration/replication-active
|
||||
integration/replication-multimaster
|
||||
integration/aof
|
||||
integration/rdb
|
||||
integration/convert-zipmap-hash-on-load
|
||||
|
||||
@@ -238,7 +238,7 @@ start_server {tags {"bitops"}} {
|
||||
r set a "abcdefg"
|
||||
r bitop lshift x a 8
|
||||
r get x
|
||||
} "\000abcdefg"
|
||||
} "\x00abcdefg"
|
||||
|
||||
test {BITOP lshift char} {
|
||||
r set a "\xAA"
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
start_server {tags {"CRON"}} {
|
||||
test {keydb.cron singleshot past tense} {
|
||||
r flushall
|
||||
r keydb.cron testjob single 0 1 {redis.call("incr", "testkey")} 1 testkey
|
||||
after 300
|
||||
assert_equal 1 [r get testkey]
|
||||
assert_equal 0 [r exists testjob]
|
||||
}
|
||||
|
||||
test {keydb.cron repeat past tense next exec is in the future} {
|
||||
r flushall
|
||||
r keydb.cron testjob repeat 0 1000000 {redis.call("incr", "testkey")} 1 testkey
|
||||
after 300
|
||||
assert_equal 1 [r get testkey]
|
||||
assert_equal 1 [r exists testjob]
|
||||
r del testjob
|
||||
}
|
||||
|
||||
test {keydb.cron repeat works} {
|
||||
r flushall
|
||||
r keydb.cron testjob repeat 0 600 {redis.call("incr","testkey")}
|
||||
after 1000
|
||||
assert_equal 2 [r get testkey]
|
||||
}
|
||||
|
||||
test {keydb.cron overwrite works} {
|
||||
r flushall
|
||||
r keydb.cron testjob single 500 {redis.call("set","testkey","a")} 1 testkey
|
||||
r keydb.cron testjob single 500 {redis.call("set","anotherkey","b")} 1 anotherkey
|
||||
after 1000
|
||||
assert_equal 0 [r exists testkey]
|
||||
assert_equal b [r get anotherkey]
|
||||
}
|
||||
|
||||
test {keydb.cron delete key stops job} {
|
||||
r flushall
|
||||
r keydb.cron testjob single 500 {redis.call("set","testkey","a")}
|
||||
r del testjob
|
||||
after 1000
|
||||
assert_equal 0 [r exists testkey]
|
||||
}
|
||||
|
||||
test {keydb.cron zero interval rejected} {
|
||||
catch {r keydb.cron testjob single 0 0 {redis.call("incr","testkey")} 1 testkey} e
|
||||
assert_match {ERR*} $e
|
||||
}
|
||||
}
|
||||
@@ -219,37 +219,4 @@ start_server {tags {"expire"}} {
|
||||
set ttl [r ttl foo]
|
||||
assert {$ttl <= 98 && $ttl > 90}
|
||||
}
|
||||
|
||||
test { EXPIREMEMBER works (set) } {
|
||||
r flushall
|
||||
r sadd testkey foo bar baz
|
||||
r expiremember testkey foo 1
|
||||
after 1500
|
||||
assert_equal {2} [r scard testkey]
|
||||
}
|
||||
|
||||
test { EXPIREMEMBER works (hash) } {
|
||||
r flushall
|
||||
r hset testkey foo bar
|
||||
r expiremember testkey foo 1
|
||||
after 1500
|
||||
r exists testkey
|
||||
} {0}
|
||||
|
||||
test { EXPIREMEMBER works (zset) } {
|
||||
r flushall
|
||||
r zadd testkey 1 foo
|
||||
r zadd testkey 2 bar
|
||||
assert_equal {2} [r zcard testkey]
|
||||
r expiremember testkey foo 1
|
||||
after 1500
|
||||
assert_equal {1} [r zcard testkey]
|
||||
}
|
||||
|
||||
test { TTL for subkey expires works } {
|
||||
r flushall
|
||||
r sadd testkey foo bar baz
|
||||
r expiremember testkey foo 10000
|
||||
assert [expr [r ttl testkey foo] > 0]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
test "incr of expired key on replica doesn't cause a crash" {
|
||||
r debug force-master yes
|
||||
r set testkey 1
|
||||
r pexpire testkey 1
|
||||
after 500
|
||||
r incr testkey
|
||||
r incr testkey
|
||||
r debug force-master no
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
start_server {tags {"rreplay"} overrides {active-replica yes}} {
|
||||
start_server {tags {"rreplay"}} {
|
||||
|
||||
test {RREPLAY use current db} {
|
||||
r debug force-master yes
|
||||
r debug force-master
|
||||
r select 4
|
||||
r set dbnum invalid
|
||||
r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$5\r\ndbnum\r\n\$4\r\nfour\r\n"
|
||||
@@ -10,7 +10,7 @@ start_server {tags {"rreplay"} overrides {active-replica yes}} {
|
||||
reconnect
|
||||
|
||||
test {RREPLAY db different} {
|
||||
r debug force-master yes
|
||||
r debug force-master
|
||||
r select 4
|
||||
r set testkey four
|
||||
r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$7\r\ntestkey\r\n\$4\r\nbebe\r\n" 2
|
||||
|
||||
@@ -156,7 +156,7 @@ mkdir -p "$REDIS_DATA_DIR" || die "Could not create redis data directory"
|
||||
|
||||
#render the templates
|
||||
TMP_FILE="/tmp/${REDIS_PORT}.conf"
|
||||
DEFAULT_CONFIG="${SCRIPTPATH}/../keydb.conf"
|
||||
DEFAULT_CONFIG="${SCRIPTPATH}/../redis.conf"
|
||||
INIT_TPL_FILE="${SCRIPTPATH}/redis_init_script.tpl"
|
||||
INIT_SCRIPT_DEST="/etc/init.d/redis_${REDIS_PORT}"
|
||||
PIDFILE="/var/run/redis_${REDIS_PORT}.pid"
|
||||
|
||||
Reference in new issue
Block a user