Compare commits

...
96 Commits
Author SHA1 Message Date
John Sully fa6870a268 Merge branch 'unstable' into RELEASE_5 2020-03-27 14:36:35 -04:00
John Sully c3d3c82f3a Raspberry Pi fixes (compile and replication) 2020-03-27 12:46:25 -04:00
John Sully f534b5b3a2 OS X Build break fix 2020-03-26 15:21:35 -04:00
John Sully 27da034819 bump version 2020-03-26 01:19:21 -04:00
John Sully 6ec1641294 Fix issue #143 2020-03-25 21:55:31 -04:00
John Sully 7b79ec360b Give a better error when handling std::terminate 2020-03-25 16:27:24 -04:00
John Sully 56fc6b7deb Add roundtrip test for subkey expires 2020-03-25 15:34:30 -04:00
John Sully 528a43bd6c Fix failure to load subkey expires 2020-03-25 01:38:58 -04:00
John Sully b357803362 Expire entry needs to be resorted after a subkey expires 2020-03-25 01:06:40 -04:00
John Sully 448808529a Unstable branch should always be version 0.0.0 2020-03-19 15:52:24 -04:00
John Sully a0bc208412 Merge branch 'RELEASE_5' into unstable 2020-03-19 15:38:16 -04:00
John Sully 6b0172b9bf Print stack traces of other threads in the deadlock detector 2020-03-19 15:37:49 -04:00
John Sully bc1eccb66d Log which thread a message came from 2020-03-19 15:37:24 -04:00
John Sully a9249d4a82 Fix lock inversion in processEventsWhileBlocked 2020-03-19 15:28:39 -04:00
John Sully 3c373494d6 Prevent dangling lock when we can't free the client 2020-03-19 14:34:23 -04:00
John Sully 5aef606d3b Fix issue #153: Config get replicaof returns a corrupt response 2020-03-16 20:43:46 -04:00
John Sully 2ec0b2cd20 Handle HTTP error codes with MOTD 2020-03-15 18:47:01 -04:00
John Sully cd76a71de3 Update MOTD URL 2020-03-15 18:32:05 -04:00
John Sully e523afa741 Fix CLANG build break 2020-03-04 17:24:00 -05:00
John Sully c2a98a60f9 Fix CLANG build break 2020-03-04 17:23:40 -05:00
John Sully 13cf8bbeb7 Merge branch 'unstable' into RELEASE_5 2020-03-04 17:13:32 -05:00
John Sully 60c0d4104d Add missing files from last checkin 2020-03-04 17:13:18 -05:00
John Sully 693ba4ed4a Merge branch 'unstable' into RELEASE_5 2020-03-04 17:09:29 -05:00
John Sully 5397f0b033 Add extra logging when reporting errors from masters - especially in rreplay 2020-03-04 17:09:12 -05:00
John Sully 6c78ec7c41 MOTD refactor 2020-03-04 16:22:55 -05:00
John Sully c0882864f2 Update README.md 2020-03-03 13:58:48 -08:00
John Sully faa87ed839 Merge branch 'unstable' into RELEASE_5 2020-03-01 21:43:21 -05:00
John Sully 83f585e30a Update tests to reflect new CRON name (keydb.cron) 2020-03-01 21:42:01 -05:00
benschermel 7a244bb847 Merge branch 'unstable' into RELEASE_5 2020-03-01 19:53:32 -05:00
John Sully 3eb847e28f Rename cron command to KEYDB.CRON and add help 2020-03-01 19:17:58 -05:00
John Sully 3ce6441a0c Bump version 2020-02-28 23:53:54 -05:00
John Sully 6d00a1a5cf Merge branch 'unstable' into RELEASE_5 2020-02-28 23:53:00 -05:00
John Sully aaca32a5cd Fix compile failure on raspberry pi machines, issue #141 2020-02-28 23:49:17 -05:00
John Sully 54c96d1995 Fix not respecting max clients config, issue #147 2020-02-28 22:04:42 -05:00
John Sully e5a2d59446 Fix issue #146 2020-02-28 21:21:05 -05:00
John Sully d95bead383 Fix crash propogating stale keys 2020-02-26 21:49:40 -05:00
John Sully 6ad6c1d780 Change Redis to KeyDB 2020-02-17 23:45:40 -05:00
John Sully 98aefac09b Add double unlock detection and improve fastlock_unlock assmebly 2020-02-17 19:54:05 -05:00
John Sully 1ef187533c processEventsWhileBlocked not exception safe 2020-02-17 19:03:49 -05:00
John Sully b9db899f6c Graceful shutdown of server threads when quit is requested 2020-02-16 18:53:46 -05:00
John Sully 06ad1c15d7 Fix memory leak in RDB load 2020-02-16 18:41:50 -05:00
John Sully 63ae679fe1 Merge branch 'unstable' of https://github.com/JohnSully/KeyDB into unstable 2020-02-16 18:12:11 -05:00
John Sully b92bbf4de8 Memory leak fix on config, and redisDb dtor 2020-02-16 17:08:00 -05:00
John Sully 4781eda722 Fix memory leak of ReplicaNestState on shutdown 2020-02-16 03:43:29 -05:00
John Sully 2fd93c5789 aeDeleteEventLoop use after free and leak fixes 2020-02-16 03:34:25 -05:00
John Sully 52434a583a Fix race condition in allocating connections to threads 2020-02-11 03:44:28 -05:00
John Sully 16a019dba0 Implement an error handler so bug #125 can't happen 2020-02-11 01:41:00 -05:00
John Sully 1a4c3224c9 Fix higher latency at low load by grouping clients to threads. This fixes slow perf in cluster benchmarks mentioned in issue #102 2020-02-11 01:00:21 -05:00
John Sully c3317686f8 Fix cases where duplicate RREPLAY is applied 2020-02-11 00:59:07 -05:00
John Sully 4622472123 Fix issue #119 2020-02-10 19:52:57 -05:00
John Sully f1748f8c76 Fix memory leak in cron 2020-02-10 18:26:04 -05:00
John Sully 0c101dccc8 Add missing test file 2020-02-10 18:15:29 -05:00
John Sully dc78bf1ccb CLANG build fix 2020-02-10 00:29:26 -05:00
John Sully a7cc3aac28 Ensure multi-master works for ring topologies 2020-02-10 00:25:03 -05:00
John Sully fb2bdf7d05 Addmissing test file 2020-02-08 16:49:41 -05:00
John Sully 050d58007f Fix issue #137 and #132 2020-02-06 23:31:31 -05:00
John Sully 49d86746ed Add test to detect issue #137 and #132 2020-02-06 23:31:12 -05:00
John Sully 1069d0b4bf CRON jobs were running half as often as required 2020-01-28 21:40:40 -05:00
John Sully c983d57f73 Missing cron files 2020-01-21 19:51:16 -05:00
John Sully 3204a39ada Initial implementation of the CRON command 2020-01-21 19:50:28 -05:00
John Sully f4319c1280 Bump version 2020-01-20 19:20:50 -05:00
John Sully 762396d997 Merge branch 'unstable' of https://github.com/JohnSully/KeyDB into unstable 2020-01-11 16:35:06 -05:00
John Sully c575e7df94 Avoid crash due to excessive posted functions for AOF rewrite 2020-01-11 16:34:30 -05:00
John Sully aa6409f2e8 Avoid crash due to excessive posted functions for AOF rewrite 2020-01-11 16:34:09 -05:00
John Sully e603e57588 Merge branch 'unstable' into RELEASE_5 2020-01-10 17:46:23 -05:00
John Sully 049b48ef79 Ignore other flags after pro is enabled since we won't understand them 2020-01-10 17:46:02 -05:00
John Sully 0ba0a6ab0a Update README.md 2020-01-10 14:21:11 -05:00
John Sully 7475ea1b0b Update graphs 2020-01-10 14:19:44 -05:00
John Sully d0654af74f Merge branch 'unstable' into RELEASE_5 2020-01-06 14:24:49 -05:00
John Sully d5f8df5997 pro launch should respect KEYDB_PRO_DIRECTORY 2020-01-06 14:24:37 -05:00
John Sully 39acb312ef Bump version 2020-01-06 12:07:57 -05:00
John Sully b643c38204 Merge branch 'unstable' into RELEASE_5 2020-01-06 12:07:11 -05:00
John Sully dd70f5efd7 Merge branch 'unstable' of https://github.com/JohnSully/KeyDB into unstable 2020-01-06 12:04:00 -05:00
John Sully bb38628f0e Add new flags to example configuration file 2020-01-05 20:19:28 -05:00
John Sully 0cf3af6857 subkey expire testes 2020-01-03 16:50:13 -05:00
John Sully edb993d52b Drop severity of master disconnect log when multimaster is enabled 2020-01-02 15:36:02 -05:00
John Sully aea333bb78 Fix issue where expire is lost when performing a defrag 2020-01-01 20:41:17 -05:00
John Sully 45817db8c3 C++ wrapper classes for SDS 2020-01-01 19:13:48 -05:00
John Sully dbef17c2e1 Fix issue #130 due to fastlock timeout reduction 2020-01-01 11:52:00 -05:00
John Sully 42a8f22c21 Fix some static analysis warnings 2019-12-27 18:17:22 -05:00
John Sully 6e06e0dfc7 Ignore dependency files 2019-12-27 17:45:56 -05:00
John Sully bf500ab44b Make incremental building work with header files 2019-12-27 17:34:41 -05:00
John Sully 4d4eca5433 Multimaster requires Active Replication 2019-12-21 18:24:02 -05:00
Petr Vaněk 4b01ad1e30 Rename redis.conf to keydb.conf 2019-12-21 15:00:24 -05:00
Petr Vaněk 3b60929bdc Rename default pid file 2019-12-21 15:00:24 -05:00
John Sully a45d141597 Implement subkey expires for sorted sets 2019-12-18 00:10:05 -05:00
John Sully 8cbe254491 Implement subkey expire for hashes 2019-12-18 00:04:48 -05:00
John Sully ab365ce3da Launch the pro binary with --enable-pro 2019-12-17 23:52:19 -05:00
John Sully aa3e70f9a0 Fix type in makefile 2019-12-17 18:23:19 -05:00
John Sully cda8d42a76 Enusre CXXFLAGS has the same settings as CFLAGS 2019-12-17 17:49:51 -05:00
Petr Vaněk 67f32bf1b1 Correct C++ related makefile variables
FINAL_CXXFLAGS should not inherit CFLAGS because they can contain C
specific options unsupported by C++.

REDIS_CXX should point to the C++ compiler rather than C.
2019-12-17 17:43:15 -05:00
Petr Vaněk 7a3f363f34 readme: fix typo 2019-12-16 15:40:09 -05:00
Tais P. Hansen 91eac2cdb3 Fix keydb-cli crashing on read-only filesystem 2019-12-09 14:23:10 -05:00
John Sully 98a928147f Fix Issue #107 2019-12-01 18:17:33 -05:00
John Sully 8a9045c028 Fix deadlock in spinlock (mfence) also spin for less time before sleeping 2019-11-30 00:51:58 -05:00
John Sully 6c65a11b8d Fastlock should yield in ARM 2019-11-28 14:26:43 -05:00
43 changed files with 1541 additions and 464 deletions

No files matched your search

+2 -1
View File
@@ -1,6 +1,7 @@
.*.swp
core
*.o
*.d
*.log
dump.rdb
redis-benchmark
@@ -27,7 +28,7 @@ release.h
src/transfer.sh
src/configs
redis.ds
src/redis.conf
src/keydb.conf
src/nodes.conf
deps/lua/src/lua
deps/lua/src/luac
+6 -2
View File
@@ -4,16 +4,20 @@
##### 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 gurantees for scripts and transactions. Because KeyDB keeps in sync with Redis development KeyDB is a superset of Redis functionality, making KeyDB a drop in replacement for existing Redis deployments.
KeyDB 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.
On the same hardware KeyDB can perform twice as many queries per second as Redis, with 60% lower latency. Active-Replication simplifies hot-spare failover allowing you to easily distribute writes over replicas and use simple TCP based load balancing/failover. KeyDB's higher performance allows you to do more on less hardware which reduces operation costs and complexity.
<img src=https://cdn-images-1.medium.com/max/1400/1*s7mTb7Qb0kxc951mz8bdgA.png width=420 height=300/><img src=https://cdn-images-1.medium.com/max/1400/1*R00A5U4AFGohGOYHMfT6fA.png height=300/>
<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/
Why fork Redis?
---------------
+127 -120
View File
@@ -1,9 +1,9 @@
# Redis configuration file example.
# KeyDB configuration file example.
#
# Note that in order to read the configuration file, Redis must be
# Note that in order to read the configuration file, KeyDB must be
# started with the file path as first argument:
#
# ./keydb-server /path/to/redis.conf
# ./keydb-server /path/to/keydb.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 Redis servers but also need
# have a standard template that goes to all KeyDB 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 Redis Sentinel. Since Redis always uses the last processed
# from admin or KeyDB Sentinel. Since KeyDB 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, Redis listens
# By default, if no "bind" configuration directive is specified, KeyDB 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 Redis is directly exposed to the
# ~~~ WARNING ~~~ If the computer running KeyDB 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 Redis to listen only into
# the IPv4 loopback interface address (this means Redis will be able to
# following bind directive, that will force KeyDB to listen only into
# the IPv4 loopback interface address (this means KeyDB 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
# Redis instances left open on the internet are accessed and exploited.
# KeyDB 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 Redis
# you are sure you want clients from other hosts to connect to KeyDB
# 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 Redis will not listen on a TCP socket.
# If port 0 is specified KeyDB 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 Redis will not listen
# incoming connections. There is no default, so KeyDB will not listen
# on a unix socket when not specified.
#
# unixsocket /tmp/redis.sock
# unixsocket /tmp/keydb.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
# Redis default starting with Redis 3.2.1.
# KeyDB default starting with Redis 3.2.1.
tcp-keepalive 300
################################# GENERAL #####################################
# 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.
# 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.
daemonize no
# If you run Redis from upstart or systemd, Redis can interact with your
# If you run KeyDB from upstart or systemd, KeyDB can interact with your
# supervision tree. Options:
# supervised no - no supervision interaction
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
# supervised upstart - signal upstart by putting KeyDB 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, Redis writes it where specified at startup
# If a pid file is specified, KeyDB 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/redis.pid".
# is used even if not specified, defaulting to "/var/run/keydb.pid".
#
# Creating a pid file is best effort: if Redis is not able to create it
# Creating a pid file is best effort: if KeyDB is not able to create it
# nothing bad happens, the server will start and run normally.
pidfile /var/run/redis_6379.pid
pidfile /var/run/keydb_6379.pid
# Specify the server verbosity level.
# This can be one of:
@@ -166,7 +166,7 @@ pidfile /var/run/redis_6379.pid
loglevel notice
# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# KeyDB 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 redis
# syslog-ident keydb
# 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 Redis shows an ASCII art logo only when started to log to the
# By default KeyDB 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 Redis will stop accepting writes if RDB snapshots are enabled
# By default KeyDB 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 Redis will
# If the background saving process will start working again KeyDB will
# automatically allow writes again.
#
# 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
# 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
# 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 Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis 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 |
# | (receive writes) | | (exact copy) |
# +------------------+ +---------------+
#
# 1) Redis replication is asynchronous, but you can configure a master to
# 1) KeyDB 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) Redis replicas are able to perform a partial resynchronization with the
# 2) KeyDB 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 Redis ACLs (for Redis version
# However this is not enough if you are using KeyDB 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 Redis master creates a new process that writes the RDB
# 1) Disk-backed: The KeyDB 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 Redis master creates a new process that directly writes the
# 2) Diskless: The KeyDB 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" Redis will use a smaller number of TCP packets and
# If you select "yes" KeyDB 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 Redis in the INFO output.
# It is used by Redis Sentinel in order to select a replica to promote into a
# 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
# 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
# Redis Sentinel for promotion.
# KeyDB 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 Redis master is able to list the address and port of the attached
# A KeyDB 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
# Redis Sentinel in order to discover replica instances.
# KeyDB 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 Redis is pretty fast an outside user can try up to
# Warning: since KeyDB 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.
# Redis ACL users are defined in the following format:
# KeyDB 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 Redis command table is described and defined.
# the KeyDB 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 redis.conf to describe users.
# format that is used inside keydb.conf to describe users.
#
# aclfile /etc/redis/users.acl
# aclfile /etc/keydb/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 Redis server is not
# this limit is set to 10000 clients, however if the KeyDB 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 Redis reserves a few file descriptors for internal uses).
# minus 32 (as KeyDB reserves a few file descriptors for internal uses).
#
# Once the limit is reached Redis will close all the new connections sending
# Once the limit is reached KeyDB 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 Redis will try to remove keys
# When the memory limit is reached KeyDB will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
#
# 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
# 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
# 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 Redis as an LRU or LFU cache, or to
# This option is usually useful when using KeyDB 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 Redis will select what to remove when maxmemory
# MAXMEMORY POLICY: how KeyDB 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, Redis will return an error on write
# Note: with any of the above policies, KeyDB 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 Redis will check five keys and pick the one that was
# accuracy. For default KeyDB 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 ####################################
# Redis has two primitives to delete keys. One is called DEL and is a blocking
# KeyDB 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 Redis. However if the key is associated with an
# O(1) or O(log_N) commands in KeyDB. 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 Redis also offers non blocking deletion primitives
# For the above reasons KeyDB 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 Redis server sometimes has to
# idea to use one or the other. However the KeyDB server sometimes has to
# delete keys or flush the whole database as a side effect of other operations.
# Specifically Redis deletes objects independently of a user call in the
# Specifically KeyDB 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 Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# 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
# 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) Redis can lose just one second of writes in a
# (see later in the config file) KeyDB 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 Redis process itself happens, but the operating system is
# wrong with the KeyDB 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 Redis will load the AOF, that is the file
# If the AOF is enabled on startup KeyDB 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.
#
# Redis supports three different modes:
# KeyDB 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
# Redis may block too long on the fsync() call. Note that there is no fix for
# KeyDB 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 Redis is
# This means that while another child is saving, the durability of KeyDB 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.
# Redis is able to automatically rewrite the log file implicitly calling
# KeyDB 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: Redis remembers the size of the AOF file after the
# This is how it works: KeyDB 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 Redis
# An AOF file may be found to be truncated at the end during the KeyDB
# startup process, when the AOF data gets loaded back into memory.
# This may happen when the system where Redis is running
# This may happen when the system where KeyDB is running
# crashes, especially when an ext4 filesystem is mounted without the
# data=ordered option (however this can't happen when Redis itself
# data=ordered option (however this can't happen when KeyDB itself
# crashes or aborts but the operating system still works correctly).
#
# Redis can either exit with an error when this happens, or load as much
# KeyDB 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 Redis server starts emitting a log to inform the user of the event.
# the KeyDB 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
# Redis will try to read more data from the AOF file but not enough bytes
# KeyDB 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, Redis is able to use an RDB preamble in the
# When rewriting the AOF file, KeyDB 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 Redis recognizes that the AOF file starts with the "REDIS"
# When loading KeyDB 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 Redis will log that a script is
# If the maximum execution time is reached KeyDB 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
################################ REDIS CLUSTER ###############################
################################ KEYDB CLUSTER ###############################
# 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
# 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
# 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 Redis nodes.
# Every Redis Cluster node requires a different cluster configuration file.
# intended to be edited by hand. It is created and updated by KeyDB nodes.
# Every KeyDB 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 Redis Cluster nodes stop accepting queries if they detect there
# By default KeyDB 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, Redis Cluster nodes address discovery fails, because
# In certain deployments, KeyDB 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 Redis Cluster working in such environments, a static
# In order to make KeyDB 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 Redis Cluster auto-detection
# If the above options are not used, the normal KeyDB 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 Redis Slow Log is a system to log queries that exceeded a specified
# The KeyDB 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 Redis
# You can configure the slow log with two parameters: one tells KeyDB
# 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 Redis latency monitoring subsystem samples different operations
# The KeyDB latency monitoring subsystem samples different operations
# at runtime in order to collect data related to possible sources of
# latency of a Redis instance.
# latency of a KeyDB 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 ##############################
# Redis can notify Pub/Sub clients about events happening in the key space.
# KeyDB 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 Redis will notify among a set
# It is possible to select the events that KeyDB 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 #################################
# Redis contains an implementation of the Gopher protocol, as specified in
# KeyDB contains an implementation of the Gopher protocol, as specified in
# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
#
# The Gopher protocol was very popular in the late '90s. It is an alternative
# to the web, and the implementation both server and client side is so simple
# that the Redis server has just 100 lines of code in order to implement this
# that the KeyDB 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 Redis, we gave it the Gopher protocol
# Anyway for the 10nth birthday of the KeyDB, we gave it the Gopher protocol
# as a gift.
#
# --- HOW IT WORKS? ---
#
# The Redis Gopher support uses the inline protocol of Redis, and specifically
# The KeyDB Gopher support uses the inline protocol of KeyDB, and specifically
# two kind of inline requests that were anyway illegal: an empty request
# or any request that starts with "/" (there are no Redis commands starting
# or any request that starts with "/" (there are no KeyDB commands starting
# with such a slash). Normal RESP2/RESP3 requests are completely out of the
# path of the Gopher protocol implementation and are served as usually as well.
#
# If you open a connection to Redis when Gopher is enabled and send it
# If you open a connection to KeyDB 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 Redis on the internet in a publicly accessible address
# If you plan to put KeyDB 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 Redis hash table (the one mapping top-level
# keys to values). The hash table implementation Redis uses (see dict.c)
# 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)
# 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 Redis can reply from time to time
# not a good thing in your environment that KeyDB 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 Redis protocol, bulk requests, that are, elements representing single
# In the KeyDB 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
# Redis calls an internal function to perform many background tasks, like
# KeyDB 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 Redis checks for
# Not all tasks are performed with the same frequency, but KeyDB 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
# Redis is idle, but at the same time will make Redis more responsive when
# KeyDB is idle, but at the same time will make KeyDB 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, Redis
# Since the default HZ value by default is conservatively set to 10, KeyDB
# 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 redis saves RDB file, if the following option is enabled
# When KeyDB 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
# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good
# KeyDB 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 Redis LFU implementation: the
# There are two tunable parameters in the KeyDB 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 Redis
# The LFU counter is just 8 bits per key, it's maximum value is 255, so KeyDB
# 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 Redis server to compact the
# Active (online) defragmentation allows a KeyDB 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) Redis will start to create new copies of the
# configuration options below) KeyDB 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 Redis
# to use the copy of Jemalloc we ship with the source code of Redis.
# 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.
# This is the default with Linux builds.
#
# 2. You never need to enable this feature if you don't have fragmentation
@@ -1561,3 +1561,10 @@ 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
+30 -10
View File
@@ -74,12 +74,18 @@ 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
ifneq (,$(findstring armv,$(uname_M)))
FINAL_LIBS+=-latomic
endif
# Backwards compatibility for selecting an allocator
ifeq ($(USE_TCMALLOC),yes)
MALLOC=tcmalloc
@@ -101,22 +107,24 @@ endif
-include .make-settings
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(CXXFLAGS) $(REDIS_CFLAGS)
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
FINAL_LIBS=-lm
FINAL_LIBS+=-lm -lcurl
DEBUG=-g -ggdb
ifeq ($(uname_S),SunOS)
# SunOS
ifneq ($(@@),32bit)
CFLAGS+= -m64
CXXFLAGS+= -m64
LDFLAGS+= -m64
endif
DEBUG=-g
DEBUG_FLAGS=-g
export CFLAGS LDFLAGS DEBUG DEBUG_FLAGS
export CFLAGS CXXFLAGS 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)
@@ -133,6 +141,7 @@ 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
@@ -150,6 +159,7 @@ else
FINAL_LDFLAGS+= -rdynamic
FINAL_LIBS+=-ldl -pthread -lrt -luuid
FINAL_CFLAGS += -DMOTD
FINAL_CXXFLAGS += -DMOTD
endif
endif
endif
@@ -187,7 +197,7 @@ ifeq ($(MALLOC),memkind)
endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
REDIS_CXX=$(QUIET_CC)$(CC) $(FINAL_CXXFLAGS)
REDIS_CXX=$(QUIET_CC)$(CXX) $(FINAL_CXXFLAGS)
KEYDB_AS=$(QUIET_CC) as --64 -g
REDIS_LD=$(QUIET_LINK)$(CXX) $(FINAL_LDFLAGS)
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
@@ -207,9 +217,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 $(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 cron.o motd.o $(ASM_OBJ)
REDIS_CLI_NAME=keydb-cli
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o $(ASM_OBJ)
REDIS_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_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
@@ -235,10 +245,13 @@ 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))
@@ -249,6 +262,8 @@ 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
@@ -274,7 +289,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) -lcurl
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)
# keydb-benchmark
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
@@ -283,20 +298,25 @@ $(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) -c $<
$(REDIS_CC) -MMD -c $<
%.o: %.cpp .make-prerequisites
$(REDIS_CXX) -c $<
$(REDIS_CXX) -MMD -c $<
%.o: %.asm .make-prerequisites
$(KEYDB_AS) $< -o $@
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
@@ -334,7 +354,7 @@ bench: $(REDIS_BENCHMARK_NAME)
@echo ""
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
@echo ""
$(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
$(MAKE) CXXFLAGS="-m32" CFLAGS="-m32" LDFLAGS="-m32"
gcov:
$(MAKE) REDIS_CFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" REDIS_LDFLAGS="-fprofile-arcs -ftest-coverage"
+4 -4
View File
@@ -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 redis.conf configuration or in the ACL file, that is:
* users in the keydb.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 redis.conf, or from the ACL file.
* the pending list of users defined in keydb.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 Redis with users defined in redis.conf and at "
"Configuring KeyDB with users defined in keydb.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 redis.conf, but not both.");
"directly in your keydb.conf, but not both.");
exit(1);
}
+22 -8
View File
@@ -84,7 +84,7 @@ fastlock g_lock("AE (global)");
#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*)0) = 1; } while(0)
#define AE_ASSERT(x) if (!(x)) do { fprintf(stderr, "AE_ASSERT FAILURE %s: %d\n", __FILE__, __LINE__); *((volatile int*)1) = 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();
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,6 +252,7 @@ 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;
@@ -272,7 +273,8 @@ int aePostFunction(aeEventLoop *eventLoop, aePostFunctionProc *proc, void *arg)
cmd.proc = proc;
cmd.clientData = arg;
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
AE_ASSERT(size == sizeof(cmd));
if (size != sizeof(cmd))
return AE_ERR;
return AE_OK;
}
@@ -289,15 +291,19 @@ 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;
@@ -384,10 +390,18 @@ 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) {
+24 -3
View File
@@ -124,6 +124,21 @@ 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);
@@ -165,7 +180,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. */
aeCreateRemoteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->aof_pipe_write_data_to_child, AE_WRITABLE, aofChildWriteDiffData, NULL, FALSE);
installAofRewriteEvent();
}
/* Write the buffer (possibly composed of multiple blocks) into the specified
@@ -341,6 +356,9 @@ 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
@@ -1556,16 +1574,19 @@ error:
void aofClosePipes(void) {
int fdAofAckPipe = g_pserver->aof_pipe_read_ack_from_child;
aePostFunction(g_pserver->el_alf_pip_read_ack_from_child, [fdAofAckPipe]{
int res = 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;
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fdAofWritePipe]{
res = 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);
+2 -3
View File
@@ -36,7 +36,6 @@ const char *ascii_logo =
" Port: %d\n"
" PID: %ld\n"
" \n"
" Like KeyDB? Star us on GitHub! \n"
" %s\n"
" \n"
" https://github.com/JohnSully/KeyDB \n"
" \n\n";
" \n";
+11 -1
View File
@@ -295,6 +295,15 @@ 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:
@@ -623,9 +632,10 @@ void freeClusterLink(clusterLink *link) {
if (link->node)
link->node->link = NULL;
link->node = nullptr;
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [link]{
int res = aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [link]{
freeClusterLink(link);
});
serverAssert(res == AE_OK);
return;
}
if (link->fd != -1) {
+18 -3
View File
@@ -405,7 +405,7 @@ void loadServerConfigFromString(char *config) {
} else if ((!strcasecmp(argv[0],"slaveof") ||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
slaveof_linenum = linenum;
replicationAddMaster(sdsnew(argv[1]), atoi(argv[2]));
replicationAddMaster(argv[1], atoi(argv[2]));
} else if ((!strcasecmp(argv[0],"repl-ping-slave-period") ||
!strcasecmp(argv[0],"repl-ping-replica-period")) &&
argc == 2)
@@ -802,6 +802,14 @@ 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;
}
@@ -1488,8 +1496,15 @@ 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);
@@ -1727,7 +1742,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 redis.conf. If possible uses the GB, MB, KB notation. */
* inside keydb.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;
@@ -1890,7 +1905,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 redis.conf. */
* we don't want a slaveof directive inside keydb.conf. */
if (g_pserver->cluster_enabled || listLength(g_pserver->masters) == 0) {
rewriteConfigMarkAsProcessed(state,option);
return;
+135
View File
@@ -0,0 +1,135 @@
#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
View File
@@ -0,0 +1,16 @@
#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);
+10 -3
View File
@@ -177,9 +177,8 @@ 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;
}
@@ -978,7 +977,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) exit(0);
if (prepareForShutdown(flags) == C_OK) throw ShutdownException();
addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
}
@@ -1306,6 +1305,14 @@ 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;
+19 -1
View File
@@ -685,8 +685,26 @@ 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 == 2) {
} else if (!strcasecmp(szFromObj(c->argv[1]), "force-master") && c->argc == 3) {
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);
+15 -5
View File
@@ -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);
void replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey);
bool 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;
}
void replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
auto itr = set.find(oldkey);
if (itr != set.end())
{
@@ -415,7 +415,10 @@ void 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) {
@@ -777,16 +780,22 @@ 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()) {
replaceSateliteOSetKeyPtr(*db->setexpire, keysds, newsds);
if (!db->setexpire->empty()) {
bool fReplaced = replaceSateliteOSetKeyPtr(*db->setexpire, keysds, newsds);
serverAssert(fReplaced == ob->FExpires());
} else {
serverAssert(!ob->FExpires());
}
}
/* Try to defrag robj and / or string value. */
ob = (robj*)dictGetVal(de);
if ((newob = activeDefragStringOb(ob, &defragged))) {
de->v.val = newob;
ob = newob;
@@ -839,6 +848,7 @@ long defragKey(redisDb *db, dictEntry *de) {
} else {
serverPanic("Unknown object type");
}
return defragged;
}
+50 -2
View File
@@ -31,6 +31,7 @@
*/
#include "server.h"
#include "cron.h"
void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
robj *keyobj = createStringObject(key,sdslen(key));
@@ -100,9 +101,32 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
}
}
break;
case OBJ_LIST:
case OBJ_ZSET:
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:
default:
serverAssert(false);
}
@@ -111,6 +135,15 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
if (deleted)
{
if (!pfat->FEmpty())
{
// We need to resort the expire entry since it may no longer be in the correct position
auto itr = db->setexpire->find(e.key());
expireEntry eT = std::move(e);
db->setexpire->erase(itr);
db->setexpire->insert(eT);
}
robj objT;
switch (val->type)
{
@@ -161,6 +194,7 @@ void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, lo
return;
}
double dblT;
switch (val->type)
{
case OBJ_SET:
@@ -170,6 +204,20 @@ 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;
+103 -56
View File
@@ -27,6 +27,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fmacros.h"
#include "fastlock.h"
#include <unistd.h>
#include <sys/syscall.h>
@@ -43,6 +44,7 @@
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include "config.h"
#ifdef __APPLE__
#include <TargetConditionals.h>
@@ -60,6 +62,11 @@
#define UNUSED(x) ((void)x)
#endif
#ifdef HAVE_BACKTRACE
#include <ucontext.h>
__attribute__((weak)) void logStackTrace(ucontext_t *) {}
#endif
extern int g_fInCrash;
/****************************************************
@@ -74,6 +81,10 @@ 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". */
@@ -145,6 +156,43 @@ __attribute__((weak)) void serverLog(int , const char *fmt, ...)
printf("\n");
}
extern "C" pid_t gettid()
{
static thread_local int pidCache = -1;
#ifdef __linux__
if (pidCache == -1)
pidCache = syscall(SYS_gettid);
#else
if (pidCache == -1) {
uint64_t tidT;
pthread_threadid_np(nullptr, &tidT);
assert(tidT < UINT_MAX);
pidCache = (int)tidT;
}
#endif
return pidCache;
}
void printTrace()
{
#ifdef HAVE_BACKTRACE
serverLog(3 /*LL_WARNING*/, "printing backtrace for thread %d", gettid());
ucontext_t ctxt;
getcontext(&ctxt);
logStackTrace(&ctxt);
#endif
}
#ifdef __linux__
static int futex(volatile unsigned *uaddr, int futex_op, int val,
const struct timespec *timeout, int val3)
{
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr, val3);
}
#endif
class DeadlockDetector
{
std::map<pid_t, fastlock *> m_mapwait;
@@ -152,9 +200,19 @@ class DeadlockDetector
public:
void registerwait(fastlock *lock, pid_t thispid)
{
static volatile bool fInDeadlock = false;
if (lock == &m_lock || g_fInCrash)
return;
fastlock_lock(&m_lock);
if (fInDeadlock)
{
printTrace();
fastlock_unlock(&m_lock);
return;
}
m_mapwait.insert(std::make_pair(thispid, lock));
// Detect cycles
@@ -180,6 +238,19 @@ public:
if (pidCheck == thispid)
break;
}
// Wake All sleeping threads so they can print their callstacks
#ifdef HAVE_BACKTRACE
#ifdef __linux__
int mask = -1;
fInDeadlock = true;
fastlock_unlock(&m_lock);
futex(&lock->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask);
futex(&itr->second->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask);
sleep(2);
fastlock_lock(&m_lock);
printTrace();
#endif
#endif
serverLog(3 /*LL_WARNING*/, "!!! KeyDB Will Now Crash !!!");
_serverPanic(__FILE__, __LINE__, "Deadlock detected");
}
@@ -206,6 +277,11 @@ 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;
@@ -213,32 +289,6 @@ uint64_t fastlock_getlongwaitcount()
return rval;
}
#ifdef __linux__
static int futex(volatile unsigned *uaddr, int futex_op, int val,
const struct timespec *timeout, int val3)
{
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr, val3);
}
#endif
extern "C" pid_t gettid()
{
static thread_local int pidCache = -1;
#ifdef __linux__
if (pidCache == -1)
pidCache = syscall(SYS_gettid);
#else
if (pidCache == -1) {
uint64_t tidT;
pthread_threadid_np(nullptr, &tidT);
assert(tidT < UINT_MAX);
pidCache = (int)tidT;
}
#endif
return pidCache;
}
extern "C" void fastlock_sleep(fastlock *lock, pid_t pid, unsigned wake, unsigned mask)
{
#ifdef __linux__
@@ -279,7 +329,7 @@ extern "C" void fastlock_lock(struct fastlock *lock)
int tid = gettid();
unsigned myticket = __atomic_fetch_add(&lock->m_ticket.m_avail, 1, __ATOMIC_RELEASE);
unsigned mask = (1U << (myticket % 32));
int cloops = 0;
unsigned cloops = 0;
ticket ticketT;
for (;;)
@@ -289,9 +339,11 @@ extern "C" void fastlock_lock(struct fastlock *lock)
break;
#if defined(__i386__) || defined(__amd64__)
__asm__ ("pause");
__asm__ __volatile__ ("pause");
#elif defined(__aarch64__)
__asm__ __volatile__ ("yield");
#endif
if ((++cloops % 1024*1024) == 0)
if ((++cloops % 0x100000) == 0)
{
fastlock_sleep(lock, tid, ticketT.u, mask);
}
@@ -324,7 +376,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, &ticket_expect, &ticket_setiflocked, fWeak /*weak*/, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
if (__atomic_compare_exchange(&lock->m_ticket.u, &ticket_expect.u, &ticket_setiflocked.u, fWeak /*weak*/, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
{
lock->m_depth = 1;
tid = gettid();
@@ -335,31 +387,6 @@ 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;
@@ -382,6 +409,26 @@ 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
@@ -411,4 +458,4 @@ void fastlock_lock_recursive(struct fastlock *lock, int nesting)
{
fastlock_lock(lock);
lock->m_depth = nesting;
}
}
+10 -23
View File
@@ -126,6 +126,7 @@ fastlock_trylock:
.ALIGN 16
.global fastlock_unlock
.type fastlock_unlock,@function
fastlock_unlock:
# RDI points to the struct:
# int32_t m_pidOwner
@@ -133,33 +134,19 @@ fastlock_unlock:
# [rdi+64] ...
# uint16_t active
# uint16_t avail
push r11
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 ecx, [rdi+64] # get current active (this one)
inc ecx # bump it to the next thread
mov [rdi+64], cx # give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
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 r9d, 1 # eax is the bitmask for 2 threads
rol r9d, cl # place the mask in the right spot for the next 2 threads
add rdi, 64 # rdi now points to the token
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
.LRetryWake:
mov r11d, [rdi+4] # 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:
pop r11
js fastlock_panic # panic if we made m_depth negative
ret
+7 -1
View File
@@ -1168,7 +1168,13 @@ 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" }
"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"
}
};
#endif
+1 -1
View File
@@ -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 redis.conf for
* Notice that it is not necessary to enable notifications in keydb.conf for
* module notifications to work.
*
* Warning: the notification callbacks are performed in a synchronous manner,
+132
View File
@@ -0,0 +1,132 @@
#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
View File
@@ -0,0 +1,14 @@
#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
+6 -2
View File
@@ -28,6 +28,7 @@
*/
#include "server.h"
bool FInReplicaReplay();
/* ================================ MULTI/EXEC ============================== */
@@ -172,12 +173,15 @@ 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))) {
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)) && !(FInReplicaReplay())) {
execCommandPropagateMulti(c);
must_propagate = 1;
}
call(c,g_pserver->loading ? CMD_CALL_NONE : CMD_CALL_FULL);
int flags = g_pserver->loading ? CMD_CALL_NONE : CMD_CALL_FULL;
if (FInReplicaReplay())
flags &= ~CMD_CALL_PROPAGATE;
call(c,flags);
/* Commands may alter argc/argv, restore mstate. */
c->mstate.commands[j].argc = c->argc;
+130 -18
View File
@@ -177,6 +177,7 @@ 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);
@@ -432,6 +433,34 @@ 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:
*
@@ -464,6 +493,12 @@ 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);
printf("\tquerybuf: %s\n", str.c_str());
}
c->master_error = 1;
}
}
@@ -1003,6 +1038,27 @@ 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;
@@ -1105,7 +1161,25 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
if (!g_fTestMode)
{
// We always accept on the same thread
{
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);
}
LLocalThread:
aeAcquireLock();
acceptCommonHandler(cfd,0,cip, ielCur);
@@ -1122,10 +1196,15 @@ 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);
aePostFunction(g_pserver->rgthreadvar[iel].el, [cfd, iel, szT]{
int res = aePostFunction(g_pserver->rgthreadvar[iel].el, [cfd, iel, szT]{
acceptCommonHandler(cfd,0,szT, iel);
zfree(szT);
});
if (res != AE_OK)
{
zfree(szT);
goto LLocalThread;
}
}
}
}
@@ -1151,13 +1230,16 @@ 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
{
aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [cfd, ielTarget]{
int res = aePostFunction(g_pserver->rgthreadvar[ielTarget].el, [cfd, ielTarget]{
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL, ielTarget);
});
if (res != AE_OK)
goto LLocalThread;
}
aeReleaseLock();
@@ -1551,7 +1633,10 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
c->lock.lock();
ae.arm(c);
if (c->flags & CLIENT_CLOSE_ASAP)
freeClient(c);
{
if (!freeClient(c))
c->lock.unlock();
}
}
}
@@ -2529,7 +2614,7 @@ NULL
{
int iel = client->iel;
freeClientAsync(client);
aePostFunction(g_pserver->rgthreadvar[client->iel].el, [iel] {
aePostFunction(g_pserver->rgthreadvar[client->iel].el, [iel] { // note: failure is OK
freeClientsInAsyncFreeQueue(iel);
});
}
@@ -3018,28 +3103,55 @@ 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)
std::vector<client*> vecclients;
listIter li;
listNode *ln;
listRewind(g_pserver->clients, &li);
// All client locks must be acquired *after* the global lock is reacquired to prevent deadlocks
// so unlock here, and save them for reacquisition later
while ((ln = listNext(&li)) != nullptr)
{
serverAssert(c->flags & CLIENT_PROTECTED);
c->lock.unlock();
client *c = (client*)listNodeValue(ln);
if (c->lock.fOwnLock()) {
serverAssert(c->flags & CLIENT_PROTECTED); // If the client is not protected we have no gurantee they won't be free'd in the event loop
c->lock.unlock();
vecclients.push_back(c);
}
}
aeReleaseLock();
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;
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;
}
}
catch (...)
{
// Caller expects us to be locked so fix and rethrow
AeLocker locker;
locker.arm(nullptr);
locker.release();
for (client *c : vecclients)
c->lock.lock();
throw;
}
AeLocker locker;
if (c != nullptr)
c->lock.lock();
locker.arm(c);
locker.arm(nullptr);
locker.release();
for (client *c : vecclients)
c->lock.lock();
return count;
}
+2
View File
@@ -29,6 +29,7 @@
*/
#include "server.h"
#include "cron.h"
#include <math.h>
#include <ctype.h>
@@ -369,6 +370,7 @@ 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());
+55 -4
View File
@@ -33,6 +33,7 @@
#include "endianconv.h"
#include "stream.h"
#include "storage.h"
#include "cron.h"
#include <math.h>
#include <sys/types.h>
@@ -523,6 +524,11 @@ 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);
}
@@ -657,6 +663,8 @@ 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");
}
@@ -986,6 +994,17 @@ 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");
}
@@ -1526,9 +1545,15 @@ 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) return NULL;
if (rdbLoadBinaryDoubleValue(rdb,&score) == -1) {
sdsfree(sdsele);
return NULL;
}
} else {
if (rdbLoadDoubleValue(rdb,&score) == -1) return NULL;
if (rdbLoadDoubleValue(rdb,&score) == -1) {
sdsfree(sdsele);
return NULL;
}
}
/* Don't care about integer-encoded strings. */
@@ -1848,6 +1873,18 @@ 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);
}
@@ -1919,7 +1956,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 = mstime();
long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now;
long long lru_clock = 0;
uint64_t mvcc_tstamp = OBJ_MVCC_INVALID;
robj *subexpireKey = nullptr;
@@ -2058,7 +2095,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
incrRefCount(subexpireKey);
} else if (!strcasecmp(szFromObj(auxkey), "keydb-subexpire-when")) {
if (key == nullptr || subexpireKey == nullptr) {
serverLog(LL_WARNING, "Corrupt subexpire entry in RDB skipping.");
serverLog(LL_WARNING, "Corrupt subexpire entry in RDB skipping. key: %s subkey: %s", key != nullptr ? szFromObj(key) : "(null)", subexpireKey != nullptr ? szFromObj(subexpireKey) : "(null)");
}
else {
setExpire(NULL, db, key, subexpireKey, strtoll(szFromObj(auxval), nullptr, 10));
@@ -2160,7 +2197,10 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
}
if (key != nullptr)
{
decrRefCount(key);
key = nullptr;
}
if (subexpireKey != nullptr)
{
@@ -2187,6 +2227,17 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
return C_OK;
eoferr: /* unexpected end of file is handled here with a fatal exit */
if (key != nullptr)
{
decrRefCount(key);
key = nullptr;
}
if (subexpireKey != nullptr)
{
decrRefCount(subexpireKey);
subexpireKey = nullptr;
}
serverLog(LL_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
rdbExitReportCorruptRDB("Unexpected EOF reading RDB file");
return C_ERR; /* Just to avoid warning */
+4 -1
View File
@@ -91,10 +91,13 @@
#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))
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 15) || (t == RDB_TYPE_CRON))
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */
+5 -111
View File
@@ -57,6 +57,7 @@
#include "anet.h"
#include "ae.h"
#include "storage.h"
#include "motd.h"
#include "redis-cli.h"
@@ -92,6 +93,9 @@ 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
*--------------------------------------------------------------------------- */
@@ -6536,116 +6540,6 @@ 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()
*--------------------------------------------------------------------------- */
@@ -6816,7 +6710,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();
char *szMotd = fetchMOTD(1 /* cache */);
if (szMotd != NULL) {
printf("Message of the day:\n %s\n", szMotd);
sdsfree(szMotd);
+84 -35
View File
@@ -42,6 +42,8 @@
#include <algorithm>
#include <uuid/uuid.h>
#include <chrono>
#include <unordered_map>
#include <string>
void replicationDiscardCachedMaster(redisMaster *mi);
void replicationResurrectCachedMaster(redisMaster *mi, int newfd);
@@ -276,7 +278,7 @@ void replicationFeedSlave(client *replica, int dictid, robj **argv, int argc, bo
if (g_pserver->repl_backlog && fSendRaw) feedReplicationBacklogWithObject(selectcmd);
/* Send it to slaves */
addReply(replica,selectcmd);
addReplyAsync(replica,selectcmd);
if (dictid < 0 || dictid >= PROTO_SHARED_SELECT_CMDS)
decrRefCount(selectcmd);
@@ -288,12 +290,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. */
addReplyArrayLen(replica,argc);
addReplyArrayLenAsync(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++)
addReplyBulk(replica,argv[j]);
addReplyBulkAsync(replica,argv[j]);
}
/* Propagate write commands to slaves, and populate the replication backlog
@@ -353,9 +355,10 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
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);
int cchMvccNum = snprintf(szMvcc, sizeof(szMvcc), "%" PRIu64, mvccTstamp);
int cchMvcc = snprintf(szMvcc, sizeof(szMvcc), "$%d\r\n%" PRIu64 "\r\n", cchMvccNum, mvccTstamp);
cchMvcc = std::min<int>(cchMvcc, sizeof(szMvcc)); // tricky snprintf
/* Write the command to the replication backlog if any. */
@@ -432,6 +435,7 @@ 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);
@@ -1255,19 +1259,19 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type)
{
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
// 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;
listNode *ln;
listRewind(g_pserver->slaves,&li);
bool fFound = false;
while ((ln = listNext(&li))) {
if (listNodeValue(ln) == replica) {
fFound = true;
break;
}
}
if (!fFound)
return;
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);
@@ -2298,7 +2302,8 @@ int connectWithMaster(redisMaster *mi) {
fd = anetTcpNonBlockBestEffortBindConnect(NULL,
mi->masterhost,mi->masterport,NET_FIRST_BIND_ADDR);
if (fd == -1) {
serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
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",
strerror(errno));
return C_ERR;
}
@@ -2324,10 +2329,11 @@ int connectWithMaster(redisMaster *mi) {
void undoConnectWithMaster(redisMaster *mi) {
int fd = mi->repl_transfer_s;
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fd]{
int res = 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;
}
@@ -3056,12 +3062,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 redis.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 keydb.conf to a larger value.");
cancelReplicationHandshake(mi);
}
/* Timed out master when we are an already connected replica? */
if (mi->masterhost && mi->repl_state == REPL_STATE_CONNECTED &&
if (mi->masterhost && mi->master && 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...");
@@ -3343,16 +3349,40 @@ 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;
}
struct RemoteMasterState
{
uint64_t mvcc = 0;
client *cFake = nullptr;
};
static std::unordered_map<std::string, RemoteMasterState> g_mapremote;
void replicaReplayCommand(client *c)
{
static thread_local ReplicaNestState *s_pstate = nullptr;
if (s_pstate == nullptr)
s_pstate = new (MALLOC_LOCAL) ReplicaNestState;
s_pstate = std::make_unique<ReplicaNestState>();
// the replay command contains two arguments:
// 1: The UUID of the source
@@ -3374,9 +3404,10 @@ void replicaReplayCommand(client *c)
return;
}
unsigned char uuid[UUID_BINARY_LEN];
std::string uuid;
uuid.resize(UUID_BINARY_LEN);
if (c->argv[1]->type != OBJ_STRING || sdslen((sds)ptrFromObj(c->argv[1])) != 36
|| uuid_parse((sds)ptrFromObj(c->argv[1]), uuid) != 0)
|| uuid_parse((sds)ptrFromObj(c->argv[1]), (unsigned char*)uuid.data()) != 0)
{
addReplyError(c, "Expected UUID arg1");
s_pstate->Cancel();
@@ -3412,7 +3443,7 @@ void replicaReplayCommand(client *c)
}
}
if (FSameUuidNoNil(uuid, cserver.uuid))
if (FSameUuidNoNil((unsigned char*)uuid.data(), cserver.uuid))
{
addReply(c, shared.ok);
s_pstate->Cancel();
@@ -3422,33 +3453,51 @@ void replicaReplayCommand(client *c)
if (!s_pstate->FPush())
return;
RemoteMasterState &remoteState = g_mapremote[uuid];
if (remoteState.cFake == nullptr)
remoteState.cFake = createClient(-1, c->iel);
else
remoteState.cFake->iel = c->iel;
client *cFake = remoteState.cFake;
if (mvcc != 0 && remoteState.mvcc >= mvcc)
{
s_pstate->Cancel();
s_pstate->Pop();
return;
}
// OK We've recieved a command lets execute
client *current_clientSave = serverTL->current_client;
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 (fExec)
if (cFake->master_error)
{
addReplyError(c, "Error in rreplay command, please check logs.");
}
if (fExec || cFake->flags & CLIENT_MULTI)
{
addReply(c, shared.ok);
selectDb(c, cFake->db->id);
redisMaster *mi = MasterInfoFromClient(c);
if (mi != nullptr) // this should never be null but I'd prefer not to crash
{
mi->mvccLastSync = mvcc;
}
if (mvcc > remoteState.mvcc)
remoteState.mvcc = mvcc;
}
else
{
serverLog(LL_WARNING, "Command didn't execute: %s", cFake->buf);
addReplyError(c, "command did not execute");
}
freeClient(cFake);
serverAssert(sdslen(cFake->querybuf) == 0);
serverTL->current_client = current_clientSave;
// call() will not propogate this for us, so we do so here
@@ -3516,4 +3565,4 @@ static void propagateMasterStaleKeys()
}
decrRefCount(rgobj[0]);
}
}
+40 -3
View File
@@ -53,11 +53,18 @@ 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(size_t string_size) {
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;
}
if (string_size < 1<<5)
return SDS_TYPE_5;
if (string_size < 1<<8)
@@ -86,10 +93,12 @@ static inline char sdsReqType(size_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, size_t initlen) {
sds sdsnewlen(const void *init, ssize_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;
@@ -137,6 +146,13 @@ sds sdsnewlen(const void *init, size_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);
@@ -161,9 +177,25 @@ 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]));
}
@@ -368,6 +400,11 @@ 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';
@@ -787,7 +824,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 sds s1, const sds s2) {
int sdscmp(const char *s1, const char *s2) {
size_t l1, l2, minlen;
int cmp;
+150 -2
View File
@@ -91,15 +91,27 @@ 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) {
@@ -121,6 +133,8 @@ 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;
@@ -148,6 +162,9 @@ 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;
}
@@ -173,6 +190,9 @@ 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;
}
}
@@ -198,6 +218,9 @@ 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;
}
}
@@ -215,6 +238,8 @@ 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;
}
@@ -237,13 +262,22 @@ 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;
}
}
sds sdsnewlen(const void *init, size_t initlen);
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 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);
@@ -265,7 +299,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 sds s1, const sds s2);
int sdscmp(const char *s1, const char *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);
@@ -298,6 +332,120 @@ 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
+85 -31
View File
@@ -35,6 +35,7 @@
#include "latency.h"
#include "atomicvar.h"
#include "storage.h"
#include "cron.h"
#include <thread>
#include <time.h>
#include <signal.h>
@@ -61,8 +62,11 @@
#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 */
@@ -1023,7 +1027,11 @@ struct redisCommand redisCommandTable[] = {
{"rreplay",replicaReplayCommand,-3,
"read-only fast noprop",
0,NULL,0,0,0,0,0,0}
0,NULL,0,0,0,0,0,0},
{"keydb.cron",cronCommand,-5,
"write use-memory",
0,NULL,1,1,1,0,0,0},
};
/*============================ Utility functions ============================ */
@@ -1031,6 +1039,7 @@ struct redisCommand redisCommandTable[] = {
/* We use a private localtime implementation which is fork-safe. The logging
* function of Redis may be called from other threads. */
extern "C" void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst);
extern "C" pid_t gettid();
/* Low level logging. To use only for very big messages, otherwise
* serverLog() is to prefer. */
@@ -1068,8 +1077,8 @@ void serverLogRaw(int level, const char *msg) {
} else {
role_char = (listLength(g_pserver->masters) ? 'S':'M'); /* Slave or Master. */
}
fprintf(fp,"%d:%c %s %c %s\n",
(int)getpid(),role_char, buf,c[level],msg);
fprintf(fp,"%d:%d:%c %s %c %s\n",
(int)getpid(),(int)gettid(),role_char, buf,c[level],msg);
}
fflush(fp);
@@ -1685,7 +1694,7 @@ 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)) goto LContinue;
if (clientsCronHandleTimeout(c,now)) continue; // Client free'd so don't release the lock
if (clientsCronResizeQueryBuffer(c)) goto LContinue;
if (clientsCronTrackExpansiveClients(c)) goto LContinue;
LContinue:
@@ -1889,7 +1898,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) exit(0);
if (prepareForShutdown(SHUTDOWN_NOFLAGS) == C_OK) throw ShutdownException();
serverLog(LL_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
g_pserver->shutdown_asap = 0;
}
@@ -2487,7 +2496,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
* redis.conf using the rename-command directive. */
* keydb.conf using the rename-command directive. */
g_pserver->commands = dictCreate(&commandTableDictType,NULL);
g_pserver->orig_commands = dictCreate(&commandTableDictType,NULL);
populateCommandTable();
@@ -2523,7 +2532,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 redis.conf. */
* Redis 5. However it is possible to revert it via keydb.conf. */
g_pserver->lua_always_replicate_commands = 1;
/* Multithreading */
@@ -2879,6 +2888,7 @@ static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
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'",
@@ -3139,7 +3149,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 redis.conf. */
* by rename-command statements in keydb.conf. */
retval2 = dictAdd(g_pserver->orig_commands, sdsnew(c->name), c);
serverAssert(retval1 == DICT_OK && retval2 == DICT_OK);
}
@@ -3214,7 +3224,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
* redis.conf rename-command statement.
* keydb.conf rename-command statement.
*
* This is used by functions rewriting the argument vector such as
* rewriteClientCommandVector() in order to set client->cmd pointer
@@ -3583,12 +3593,12 @@ int processCommand(client *c, int callFlags) {
return C_OK;
}
}
incrementMvccTstamp();
if (!locker.isArmed())
locker.arm(c);
incrementMvccTstamp();
/* Handle the maxmemory directive.
*
* Note that we do not want to reclaim memory if we are here re-entering
@@ -3806,8 +3816,17 @@ 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;
}
@@ -4684,7 +4703,7 @@ void daemonize(void) {
}
void version(void) {
printf("Redis server v=%s sha=%s:%d malloc=%s bits=%d build=%llx\n",
printf("KeyDB server v=%s sha=%s:%d malloc=%s bits=%d build=%llx\n",
KEYDB_REAL_VERSION,
redisGitSHA1(),
atoi(redisGitDirty()) > 0,
@@ -4695,7 +4714,7 @@ void version(void) {
}
void usage(void) {
fprintf(stderr,"Usage: ./keydb-server [/path/to/redis.conf] [options]\n");
fprintf(stderr,"Usage: ./keydb-server [/path/to/keydb.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");
@@ -4705,7 +4724,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/myredis.conf --loglevel verbose\n\n");
fprintf(stderr," ./keydb-server /etc/mykeydb.conf --loglevel verbose\n\n");
fprintf(stderr,"Sentinel mode:\n");
fprintf(stderr," ./keydb-server /etc/sentinel.conf --sentinel\n");
exit(1);
@@ -4722,7 +4741,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 redis.conf. */
* forced us to do so via keydb.conf. */
int show_logo = ((!g_pserver->syslog_enabled &&
g_pserver->logfile[0] == '\0' &&
isatty(fileno(stdout))) ||
@@ -4734,14 +4753,18 @@ 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()
(long) getpid(),
motd ? motd : ""
);
if (motd)
sdsfree(motd);
serverLogRaw(LL_NOTICE|LL_RAW,buf);
}
zfree(buf);
@@ -4995,14 +5018,15 @@ void incrementMvccTstamp()
msPrev >>= MVCC_MS_SHIFT; // convert to milliseconds
long long mst;
__atomic_load(&g_pserver->mstime, &mst, __ATOMIC_RELAXED);
__atomic_load(&g_pserver->mstime, &mst, __ATOMIC_ACQUIRE);
if (msPrev >= (uint64_t)mst) // we can be greater if the count overflows
{
atomicIncr(g_pserver->mvcc_tstamp, 1);
__atomic_fetch_add(&g_pserver->mvcc_tstamp, 1, __ATOMIC_RELEASE);
}
else
{
atomicSet(g_pserver->mvcc_tstamp, ((uint64_t)g_pserver->mstime) << MVCC_MS_SHIFT);
uint64_t val = ((uint64_t)g_pserver->mstime) << MVCC_MS_SHIFT;
__atomic_store(&g_pserver->mvcc_tstamp, &val, __ATOMIC_RELEASE);
}
}
@@ -5013,7 +5037,7 @@ void OnTerminate()
The easiest way to achieve that is to acutally segfault, so we assert
here.
*/
serverAssert(false);
serverPanic("std::teminate() called");
}
void *workerThreadMain(void *parg)
@@ -5027,11 +5051,34 @@ 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);
aeMain(el);
try
{
aeMain(el);
}
catch (ShutdownException)
{
}
serverAssert(!GlobalLocksAcquired());
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;
@@ -5083,10 +5130,6 @@ 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
@@ -5183,6 +5226,15 @@ 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",
@@ -5198,16 +5250,16 @@ int main(int argc, char **argv) {
serverLog(LL_WARNING, "Configuration loaded");
}
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
}
validateConfiguration();
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 */);
@@ -5308,7 +5360,9 @@ 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;
pthread_join(rgthread[IDX_EVENT_LOOP_MAIN], &pvRet);
for (int iel = 0; iel < cserver.cthreads; ++iel)
pthread_join(rgthread[iel], &pvRet);
return 0;
}
+19 -5
View File
@@ -55,6 +55,7 @@
#include <algorithm>
#include <memory>
#include <map>
#include <string>
#ifdef __cplusplus
extern "C" {
#include <lua.h>
@@ -264,7 +265,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/redis.pid"
#define CONFIG_DEFAULT_PID_FILE "/var/run/keydb.pid"
#define CONFIG_DEFAULT_SYSLOG_IDENT "redis"
#define CONFIG_DEFAULT_CLUSTER_CONFIG_FILE "nodes.conf"
#define CONFIG_DEFAULT_CLUSTER_ANNOUNCE_IP NULL /* Auto detect. */
@@ -683,6 +684,8 @@ 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
@@ -1125,10 +1128,13 @@ 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. */
typedef struct redisDb {
struct redisDb {
redisDb()
: expireitr(nullptr)
{};
~redisDb();
dict *pdict; /* The keyspace for this DB */
expireset *setexpire;
expireset::setiter expireitr;
@@ -1140,7 +1146,7 @@ typedef 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. */
} redisDb;
};
/* Client MULTI/EXEC state */
typedef struct multiCmd {
@@ -1332,6 +1338,7 @@ typedef struct client {
int iel; /* the event loop index we're registered with */
struct fastlock lock;
int master_error;
} client;
struct saveparam {
@@ -1524,6 +1531,7 @@ struct redisServerThreadVars {
struct fastlock lockPendingWrite { "thread pending write" };
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
long unsigned commandsExecuted = 0;
bool fRetrySetAofEvent = false;
};
struct redisMaster {
@@ -1578,7 +1586,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 redis.conf */
int verbosity; /* Loglevel in keydb.conf */
int maxidletime; /* Client timeout in seconds */
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
int active_defrag_enabled;
@@ -1599,6 +1607,8 @@ 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 {
@@ -1732,6 +1742,7 @@ 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 */
@@ -1829,7 +1840,7 @@ struct redisServer {
int sort_alpha;
int sort_bypattern;
int sort_store;
/* Zip structure config, see redis.conf for more information */
/* Zip structure config, see keydb.conf for more information */
size_t hash_max_ziplist_entries;
size_t hash_max_ziplist_value;
size_t set_max_intset_entries;
@@ -2911,6 +2922,9 @@ 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
View File
@@ -1,3 +1,3 @@
#define KEYDB_REAL_VERSION "5.2.0"
#define KEYDB_REAL_VERSION "5.3.3"
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
+18
View File
@@ -59,6 +59,24 @@ 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
@@ -0,0 +1,74 @@
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"
}
}
}
}
}
}
}
+2
View File
@@ -309,3 +309,5 @@ start_server {tags {"repl"}} {
}
}
}
+3
View File
@@ -36,6 +36,8 @@ set ::all_tests {
unit/aofrw
unit/acl
unit/rreplay
unit/cron
unit/replication
integration/block-repl
integration/replication
integration/replication-2
@@ -43,6 +45,7 @@ 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
+47
View File
@@ -0,0 +1,47 @@
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
}
}
+42
View File
@@ -219,4 +219,46 @@ 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]
}
test {Roundtrip for subkey expires works} {
r flushall
r sadd testkey foo bar baz
r expiremember testkey foo 10000
r save
r debug reload
assert [expr [r ttl testkey foo] > 0]
}
}
+12
View File
@@ -0,0 +1,12 @@
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
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
start_server {tags {"rreplay"}} {
start_server {tags {"rreplay"} overrides {active-replica yes}} {
test {RREPLAY use current db} {
r debug force-master
r debug force-master yes
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"}} {
reconnect
test {RREPLAY db different} {
r debug force-master
r debug force-master yes
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
+1 -1
View File
@@ -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}/../redis.conf"
DEFAULT_CONFIG="${SCRIPTPATH}/../keydb.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"