Compare commits

..
172 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
John Sully 7dd53182e0 Bump version 2019-11-21 20:16:42 -05:00
John Sully 53b95d830a Merge branch 'unstable' into RELEASE_5 2019-11-21 20:16:23 -05:00
John Sully 3028a890ef Fix issue #83 2019-11-21 20:05:16 -05:00
John Sully 39c6fbddb1 Add missing dependency to dockerfile 2019-11-20 20:35:14 -05:00
John Sully 704cb4e929 Merge branch 'unstable' of https://github.com/JohnSully/KeyDB into unstable 2019-11-20 19:44:43 -05:00
John Sully 8e4f323439 Fix issue #107, active replicas do their own expires 2019-11-20 19:44:31 -05:00
John Sully 937702ea1d Additional asserts ensuring the client is creating on the correct thread 2019-11-20 17:00:40 -05:00
John Sully 3ea56ffb38 Update issue templates 2019-11-20 16:25:33 -05:00
John Sully becd3fc5a3 Update issue templates 2019-11-20 16:24:23 -05:00
John Sully 4c8603815c Fix failure to wakeup from futex sleep due to fastlock_unlock reading the wrong offset in the asm version. Also fix false sharing in spinlock 2019-11-17 16:06:49 -05:00
John Sully 3ae75c2d2b Revert "Debug sleep should apply to all threads"
This reverts commit 41b678814b.
2019-11-17 15:39:47 -05:00
John Sully 9371c005aa Improve perf of reading cluster bitfield 2019-11-17 14:52:12 -05:00
John Sully 16bd6b3f5d Merge branch 'unstable' of https://github.com/JohnSully/KeyDB into unstable 2019-11-14 21:21:46 -05:00
John Sully 3dd78a6101 Cluster multithreading fixes 2019-11-14 20:14:24 -05:00
John Sully 41b678814b Debug sleep should apply to all threads 2019-11-14 19:57:29 -05:00
John Sully d0ccb074d5 killing clients should take effect ASAP 2019-11-14 19:49:32 -05:00
John Sully cb0fc7cca2 Improve AE Assert message 2019-11-14 19:34:13 -05:00
John Sully 5e40ea6ee7 Fix test failure with tcl8.5 (test only issue) 2019-11-04 17:31:29 -08:00
John Sully 427c5999f1 Fix potential race in pubsub 2019-10-25 02:50:30 -04:00
John Sully a91f58b8fd Fix potential race in pubsub 2019-10-25 02:44:14 -04:00
John Sully 3013799664 bump version 2019-10-25 01:00:28 -04:00
John Sully f8220a658e Merge branch 'unstable' into RELEASE_5 2019-10-24 23:39:18 -04:00
John Sully f1d2b29450 Signals can happen on any thread, so look for the signal handler not the command on the callstack 2019-10-24 23:30:08 -04:00
John Sully cb93752aff Two fixes: 1) Remove race conditions by not locking clients when async writing. 2) Don't derefence dangling pointers in lambda 2019-10-24 23:07:02 -04:00
John Sully 549cad378d Sanitizers work with GCC too 2019-10-24 23:05:39 -04:00
John Sully 631f863dd8 Remove the DB lock, its unnecessary 2019-10-24 20:18:48 -04:00
John Sully 4396682c07 Remove unlocked KEYS command support, we will do this with snapshotting 2019-10-24 20:18:11 -04:00
John Sully ca4ee4e3e4 Build break, ensure C99 compatibility 2019-10-23 13:38:07 -04:00
John Sully 2da09e0732 Fix build break in TravisCI 2019-10-23 13:31:39 -04:00
John Sully 4986dc6da9 If a replica is about to be closed, don't wait on its lock (potential deadlock) 2019-10-23 13:20:23 -04:00
John Sully 1a70af2ae1 processEventsWhileBlocked needs to release the lock in a safe way 2019-10-23 13:19:57 -04:00
John Sully 5a8cb77d0d Remove race conditions 2019-10-22 23:26:37 -04:00
John Sully 600fc241cf Optimize deadlock detection, fix callstack for ASM, and annotate locks
Note: This change moves our assembly code to use the GNU Assembler because NASM seems to be incapable of emitting the necessary debug information for callstack unwinding to work.
2019-10-22 21:34:51 -04:00
John Sully fa797408d9 Implement deadlock detection 2019-10-22 00:43:32 -04:00
John Sully 3fac516950 Disable multithreaded KEYS due to bugs 2019-10-21 12:21:46 -04:00
John Sully 2f65aa2337 Merge branch 'unstable' into RELEASE_5 2019-10-16 12:47:06 -04:00
john@csquare.ca f5210a8fb0 Fix: ExpireMember does not update MVCC timestamp 2019-10-16 12:38:15 -04:00
John Sully 8f1295c00f expiremember should return the same results as expire 2019-10-16 12:27:04 -04:00
John Sully 18a3768e07 Fix crash adding expire to key with a subkey expire already set 2019-10-16 12:26:48 -04:00
eric-weaver 95e3dbc104 add missing dependency 2019-10-13 21:30:06 +02:00
Segmond 40355eac3e Update README.md
Typo, focuses spelled as focusses
2019-10-13 21:29:15 +02:00
John Sully 3a3b59a054 Implement the lastmodified query for the OBJECT command 2019-10-13 14:12:09 -04:00
John Sully 12e17cfd6e You are not allowed to set a subkey expire if the subkey does not exist 2019-10-13 14:11:43 -04:00
John Sully 203e341bf4 Implement the EXPIREMEMBERAT command 2019-10-13 13:15:13 -04:00
John Sully 6e6cd84ad7 Add millisecond support for EXPIREMEMBER command 2019-10-13 12:57:50 -04:00
John Sully 252725d50f Update more slave instances to use replica (Issue #75) 2019-10-13 12:29:20 -04:00
John Sully c15633ea9e Update a few uses of slave to use the new replica terminology (Issue #75) 2019-10-13 12:12:05 -04:00
John Sully 27c9ae667e Fix an intermittent crash when replicationCron happens before slavesdb is update 2019-10-13 12:04:54 -04:00
John Sully 16f96efbe7 Implement TTL and PERSIST commands for subkeys. Also ensure expiremember replaces any old subkey expiries 2019-10-13 12:03:25 -04:00
John Sully 5787e7adc7 Merge branch 'unstable' of https://github.com/JohnSully/redis2 into unstable 2019-10-13 10:03:01 -04:00
John Sully 4631119179 Fix issue #88 - don't let seperate KeyDB processes listen on the same port 2019-10-13 10:02:21 -04:00
John Sully 0a0c2146c8 Bump version 2019-10-01 17:53:48 -04:00
Edgard Lorraine Messias cedea7e400 Fixed "Provides" name in chkconfig info 2019-10-01 13:44:58 -04:00
John Sully 66cef6646c Merge branch 'unstable' into RELEASE_5 2019-09-30 00:28:12 -04:00
John Sully 64aa4b80e6 Expire DEL command place holder 2019-09-30 00:01:23 -04:00
John Sully 551a3cb1aa Fuzz mode is now an argument not based on the exe name 2019-09-30 00:00:29 -04:00
John Sully 0942188b0a finish up the RDB fuzz 2019-09-28 17:38:46 -04:00
John Sully 070c481871 Fix double free in RDB load 2019-09-28 17:38:26 -04:00
John Sully 6df4d29422 RDB fuzz test cases 2019-09-28 17:04:08 -04:00
John Sully 6e1e5bd08b RREPLAY command now takes a DB argument 2019-09-28 14:59:44 -04:00
John Sully ba365298ed Implement left and right shift BITOP operators 2019-09-28 00:10:46 -04:00
John Sully e1c9620951 Fix leaking stale RDBs during multimaster sync 2019-09-27 13:17:29 -04:00
John Sully e776474f68 Fix multi master bugs: 1. we fail to create the temp file. 2. We use a master RDB as our backup even though we merged databases (and therefore it is not representitive) 2019-09-26 20:35:51 -04:00
John Sully 4589e861ab Fix issue where AOF events are posted to the wrong event loop and not properly cleaned up 2019-09-19 15:40:09 -04:00
John Sully 55324aa56a Fix issue where AOF events are posted to the wrong event loop and not properly cleaned up 2019-09-19 15:39:52 -04:00
John Sully 18d42a5c35 KEYS now only blocks one database 2019-09-12 18:51:10 -04:00
John Sully 71d5a10621 bump version 2019-09-06 13:31:40 -04:00
John Sully ac595ebe37 Fix issue where Active Replicas were commiting data to the wrong database under load 2019-09-06 13:30:28 -04:00
John Sully 50a6a3ca38 Fix issue where Active Replicas were commiting data to the wrong database under load 2019-09-06 13:30:15 -04:00
John Sully f83a89f82a Acquire the lock for modules that don't acquire it before calling like they are supposed to 2019-08-29 15:41:03 -04:00
John Sully bf905a78f0 Bump version 2019-08-27 15:32:24 -04:00
John Sully fad9483fc9 Fix race condition in PUB/SUB and other async reply commands where the client can be freed before our handler is executed on the client thread. When this occurs the client pointer is dangling 2019-08-27 15:31:47 -04:00
John Sully 6b6e6f6c1e Fix crash in RediSearch 2019-07-29 15:08:57 -04:00
John Sully b0a0f03b96 RREPLAY failures should be logged 2019-07-24 22:49:55 -04:00
John Sully f1982ca63d Issue #64 RREPLAY isn't binary safe. Add fix and test. 2019-07-24 22:31:44 -04:00
John Sully 25e18a7de9 Release version 5 2019-07-18 20:50:40 -04:00
75 changed files with 3208 additions and 1433 deletions

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
** Log Files **
These should be KeyDB logs, not syslogs or logs from your container manager. If you are reporting a crash there will be a line in your log stating:
"=== KEYDB BUG REPORT START: Cut & paste starting from here ==="
Please copy everything after this line.
**To Reproduce**
Do you know how to reproduce this? If so please provide repro steps.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+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
-58
View File
@@ -1,58 +0,0 @@
{
"files.associations": {
"zmalloc.h": "c",
"stat.h": "c",
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"fstream": "cpp",
"functional": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"memory": "cpp",
"mutex": "cpp",
"new": "cpp",
"numeric": "cpp",
"optional": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"scoped_allocator": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp",
"utility": "cpp",
"set": "cpp",
"algorithm": "cpp"
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ FROM ubuntu:18.04
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -qqy \
build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev \
build-essential nasm autotools-dev autoconf libcurl4-openssl-dev libjemalloc-dev tcl tcl-dev uuid-dev libcurl4-openssl-dev \
&& apt-get clean
CMD make
+7 -3
View File
@@ -4,21 +4,25 @@
##### 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?
---------------
KeyDB has a different philosophy on how the codebase should evolve. We feel that ease of use, high performance, and a "batteries included" approach is the best way to create a good user experience. While we have great respect for the Redis maintainers it is our opinion that the Redis approach focusses too much on simplicity of the code base at the expense of complexity for the user. This results in the need for external components and workarounds to solve common problems - resulting in more complexity overall.
KeyDB has a different philosophy on how the codebase should evolve. We feel that ease of use, high performance, and a "batteries included" approach is the best way to create a good user experience. While we have great respect for the Redis maintainers it is our opinion that the Redis approach focuses too much on simplicity of the code base at the expense of complexity for the user. This results in the need for external components and workarounds to solve common problems - resulting in more complexity overall.
Because of this difference of opinion features which are right for KeyDB may not be appropriate for Redis. A fork allows us to explore this new development path and implement features which may never be a part of Redis. KeyDB keeps in sync with upstream Redis changes, and where applicable we upstream bug fixes and changes. It is our hope that the two projects can continue to grow and learn from each other.
+13
View File
@@ -0,0 +1,13 @@
="repl-stream-db"
="repl-id"
="repl-offset"
="lua"
="redis-ver"
="ctime"
="used-mem"
="aof-preamble"
="redis-bits"
="mvcc-tstamp"
="keydb-subexpire-key"
="keydb-subexpire-when"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+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
+32 -14
View File
@@ -47,8 +47,6 @@ endif
USEASM?=true
ifneq ($(SANITIZE),)
CC=clang
CXX=clang++
CFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
CXXFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
LDFLAGS+= -fsanitize=$(SANITIZE)
@@ -76,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
@@ -103,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)
@@ -135,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
@@ -152,6 +159,7 @@ else
FINAL_LDFLAGS+= -rdynamic
FINAL_LIBS+=-ldl -pthread -lrt -luuid
FINAL_CFLAGS += -DMOTD
FINAL_CXXFLAGS += -DMOTD
endif
endif
endif
@@ -189,8 +197,8 @@ ifeq ($(MALLOC),memkind)
endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
REDIS_CXX=$(QUIET_CC)$(CC) $(FINAL_CXXFLAGS)
REDIS_NASM=$(QUIET_CC)nasm -felf64
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)
@@ -209,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
@@ -237,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))
@@ -251,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
@@ -276,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)
@@ -285,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
$(REDIS_NASM) $<
$(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
@@ -336,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);
}
+24 -10
View File
@@ -80,11 +80,11 @@ public:
mutex_wrapper g_lock;
#else
fastlock g_lock;
fastlock g_lock("AE (global)");
#endif
thread_local aeEventLoop *g_eventLoopThisThread = NULL;
#define AE_ASSERT(x) if (!(x)) do { fprintf(stderr, "AE_ASSER FAILURE\n"); *((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;
@@ -327,7 +333,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
for (i = 0; i < setsize; i++)
eventLoop->events[i].mask = AE_NONE;
fastlock_init(&eventLoop->flock);
fastlock_init(&eventLoop->flock, "event loop");
int rgfd[2];
if (pipe(rgfd) < 0)
goto err;
@@ -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) {
+5
View File
@@ -61,6 +61,11 @@ public:
return m_fArmed;
}
void release()
{
m_fArmed = false;
}
~AeLocker()
{
if (m_fArmed)
+11 -7
View File
@@ -476,7 +476,7 @@ static int anetV6Only(char *err, int s) {
return ANET_OK;
}
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort)
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort, int fFirstListen)
{
int s = -1, rv;
char _port[6]; /* strlen("65535") */
@@ -498,8 +498,12 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
if (fReusePort && anetSetReusePort(err,s) == ANET_ERR) goto error;
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
if (fReusePort && !fFirstListen && anetSetReusePort(err,s) == ANET_ERR) goto error;
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) {
s = ANET_ERR;
goto end;
}
if (fReusePort && fFirstListen && anetSetReusePort(err,s) == ANET_ERR) goto error;
goto end;
}
if (p == NULL) {
@@ -515,14 +519,14 @@ end:
return s;
}
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort)
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
{
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort);
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort, fFirstListen);
}
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort)
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
{
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort);
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort, fFirstListen);
}
int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
+2 -2
View File
@@ -62,8 +62,8 @@ int anetUnixNonBlockConnect(char *err, char *path);
int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort);
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort);
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
int anetUnixAccept(char *err, int serversock);
+38 -11
View File
@@ -97,6 +97,7 @@ void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
aofrwblock *block;
ssize_t nwritten;
serverAssert(GlobalLocksAcquired());
serverAssert(el == g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el); // MUST run on main thread
UNUSED(el);
UNUSED(fd);
@@ -123,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);
@@ -164,10 +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. */
if (aeGetFileEvents(serverTL->el,g_pserver->aof_pipe_write_data_to_child) == 0) {
aeCreateFileEvent(serverTL->el, g_pserver->aof_pipe_write_data_to_child,
AE_WRITABLE, aofChildWriteDiffData, NULL);
}
installAofRewriteEvent();
}
/* Write the buffer (possibly composed of multiple blocks) into the specified
@@ -343,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
@@ -668,7 +684,7 @@ client *createFakeClient(void) {
c->flags = 0;
c->fPendingAsyncWrite = FALSE;
c->btype = BLOCKED_NONE;
/* We set the fake client as a slave waiting for the synchronization
/* We set the fake client as a replica waiting for the synchronization
* so that Redis will not try to send replies to this client. */
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
c->reply = listCreate();
@@ -680,7 +696,7 @@ client *createFakeClient(void) {
c->puser = NULL;
listSetFreeMethod(c->reply,freeClientReplyValue);
listSetDupMethod(c->reply,dupClientReplyValue);
fastlock_init(&c->lock);
fastlock_init(&c->lock, "fake client");
fastlock_lock(&c->lock);
initClientMultiState(c);
return c;
@@ -1519,7 +1535,7 @@ void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
}
/* Remove the handler since this can be called only one time during a
* rewrite. */
aeDeleteFileEventAsync(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
aeDeleteFileEvent(el,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
}
/* Create the pipes used for parent - child process IPC during rewrite.
@@ -1557,12 +1573,23 @@ error:
}
void aofClosePipes(void) {
aeDeleteFileEventAsync(g_pserver->el_alf_pip_read_ack_from_child,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
aeDeleteFileEventAsync(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,g_pserver->aof_pipe_write_data_to_child,AE_WRITABLE);
close(g_pserver->aof_pipe_write_data_to_child);
int fdAofAckPipe = g_pserver->aof_pipe_read_ack_from_child;
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;
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);
close(g_pserver->aof_pipe_read_ack_from_child);
close(g_pserver->aof_pipe_write_ack_to_child);
close(g_pserver->aof_pipe_read_ack_from_parent);
}
+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";
+177 -266
View File
@@ -396,7 +396,8 @@ void printBits(unsigned char *p, unsigned long count) {
#define BITOP_OR 1
#define BITOP_XOR 2
#define BITOP_NOT 3
#define BITOP_INVALID 4
#define BITOP_LSHIFT 4
#define BITOP_RSHIFT 5
#define BITFIELDOP_GET 0
#define BITFIELDOP_SET 1
@@ -588,277 +589,34 @@ void getbitCommand(client *c) {
addReply(c, bitval ? shared.cone : shared.czero);
}
unsigned char *bitopCore(unsigned long op, unsigned long minlen, unsigned long maxlen, unsigned long numkeys, unsigned char **src, unsigned long *len)
{
unsigned char *res = NULL; /* Resulting string. */
unsigned long j;
/* Compute the bit operation, if at least one string is not empty. */
if (maxlen) {
res = (unsigned char*) sdsnewlen(NULL,maxlen);
unsigned char output, byte;
unsigned long i;
/* Fast path: as far as we have data for all the input bitmaps we
* can take a fast path that performs much better than the
* vanilla algorithm. On ARM we skip the fast path since it will
* result in GCC compiling the code using multiple-words load/store
* operations that are not supported even in ARM >= v6. */
j = 0;
#ifndef USE_ALIGNED_ACCESS
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
unsigned long *lp[16];
unsigned long *lres = (unsigned long*) res;
/* Note: sds pointer is always aligned to 8 byte boundary. */
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
memcpy(res,src[0],minlen);
/* Different branches per different operations for speed (sorry). */
if (op == BITOP_AND) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] &= lp[i][0];
lres[1] &= lp[i][1];
lres[2] &= lp[i][2];
lres[3] &= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_OR) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] |= lp[i][0];
lres[1] |= lp[i][1];
lres[2] |= lp[i][2];
lres[3] |= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_XOR) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] ^= lp[i][0];
lres[1] ^= lp[i][1];
lres[2] ^= lp[i][2];
lres[3] ^= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_NOT) {
while(minlen >= sizeof(unsigned long)*4) {
lres[0] = ~lres[0];
lres[1] = ~lres[1];
lres[2] = ~lres[2];
lres[3] = ~lres[3];
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
}
}
#endif
/* j is set to the next byte to process by the previous loop. */
for (; j < maxlen; j++) {
output = (len[0] <= j) ? 0 : src[0][j];
if (op == BITOP_NOT) output = ~output;
for (i = 1; i < numkeys; i++) {
byte = (len[i] <= j) ? 0 : src[i][j];
switch(op) {
case BITOP_AND: output &= byte; break;
case BITOP_OR: output |= byte; break;
case BITOP_XOR: output ^= byte; break;
}
}
res[j] = output;
}
}
return res;
}
unsigned bitopFromStr(const char *opname)
{
if ((opname[0] == 'a' || opname[0] == 'A') && !strcasecmp(opname,"and"))
return BITOP_AND;
else if((opname[0] == 'o' || opname[0] == 'O') && !strcasecmp(opname,"or"))
return BITOP_OR;
else if((opname[0] == 'x' || opname[0] == 'X') && !strcasecmp(opname,"xor"))
return BITOP_XOR;
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
return BITOP_NOT;
return BITOP_INVALID;
}
// Format (long):(long)
std::pair<long long, long long> parseRange(robj *o)
{
const char *sz = szFromObj(o);
const char *pchCheck = sz;
while (*pchCheck != '\0' && *pchCheck != ':')
++pchCheck;
if (*pchCheck != ':')
throw shared.syntaxerr;
long long min,max;
if (!string2ll(sz, pchCheck - sz, &min))
throw shared.syntaxerr;
if (!string2ll(pchCheck+1, strlen(pchCheck+1), &max))
throw shared.syntaxerr;
return std::make_pair(min, max);
}
void bitopRangeCommand(client *c)
{
char *opname = szFromObj(c->argv[1]);
robj *targetkey = c->argv[2];
unsigned long minlen = 0, maxlen = 0;
/* Parse the operation name. */
unsigned long op = bitopFromStr(opname);
if (op == BITOP_INVALID) {
addReply(c,shared.syntaxerr);
return;
}
/* Sanity check: NOT accepts only a single key argument. */
if (op == BITOP_NOT && c->argc != 4) {
addReplyError(c,"BITOP NOT must be called with a single source key.");
return;
}
if ((c->argc % 2) == 0)
{ // did someone forget a range?
addReply(c,shared.syntaxerr);
return;
}
/* Lookup keys, and store pointers to the string objects into an array. */
unsigned numkeys = (c->argc - 3) / 2;
std::vector<unsigned char*> vecsrc(numkeys);
std::vector<unsigned long> veclen(numkeys);
std::vector<robj_rorefptr> vecobjects(numkeys);
std::vector<std::unique_ptr<const char, void(*)(const char*)>> vecspsz; // temp storage so we free allocated strings
for (unsigned long j = 0; j < numkeys; j++) {
size_t iarg = (j*2)+3;
robj_roptr o = lookupKeyRead(c->db,c->argv[iarg]);
/* Handle non-existing keys as empty strings. */
if (o == nullptr) {
vecobjects[j] = nullptr;
vecsrc[j] = NULL;
veclen[j] = 0;
minlen = 0;
continue;
}
/* Return an error if one of the keys is not a string. */
if (checkType(c,o,OBJ_STRING))
return;
vecobjects[j] = getDecodedObject(o);
auto range = parseRange(c->argv[iarg+1]);
if (range.first < 0 || (range.second < range.first && range.second != -1))
throw shared.syntaxerr;
vecsrc[j] = (unsigned char*)ptrFromObj(vecobjects[j]);
veclen[j] = sdslen(szFromObj(vecobjects[j]));
// Handle the offset
if ((size_t)range.first >= (veclen[j]*CHAR_BIT))
{ // the start position is longer than the string
vecsrc[j] = nullptr;
veclen[j] = 0;
}
else
{
vecsrc[j] += range.first/CHAR_BIT;
veclen[j] -= range.first/CHAR_BIT;
int bitshift = range.first % CHAR_BIT;
if (bitshift)
{
sds newstr = sdsnewlen(nullptr, veclen[j]);
for (size_t ich = 0; ich < veclen[j]; ++ich)
newstr[ich] = static_cast<unsigned char>(vecsrc[j][ich]) >> bitshift;
vecsrc[j] = (unsigned char*)newstr;
auto spT = std::unique_ptr<const char, void(*)(const char*)>(newstr, sdsfree);
vecspsz.emplace_back(std::move(spT));
}
}
// Now the max char
if (range.second != -1)
{
if (range.second < 0)
throw shared.syntaxerr;
range.second -= range.first; // make relative
int mod = range.second % CHAR_BIT;
if ((size_t)range.second < (veclen[j]*CHAR_BIT))
{
veclen[j] = range.second/CHAR_BIT;
if (mod)
veclen[j]++;
}
if (mod && veclen[j] > 0)
{
sds newstr = sdsnewlen(nullptr, veclen[j]);
memcpy(newstr, vecsrc[j], veclen[j]);
newstr[sdslen(newstr)-1] &= ((1U << mod)-1);
vecsrc[j] = (unsigned char*)newstr;
auto spT = std::unique_ptr<const char, void(*)(const char*)>(newstr, sdsfree);
vecspsz.emplace_back(std::move(spT));
}
}
if (veclen[j] > maxlen) maxlen = veclen[j];
if (j == 0 || veclen[j] < minlen) minlen = veclen[j];
}
unsigned char *res = bitopCore(op, minlen, maxlen, numkeys, vecsrc.data(), veclen.data());
/* Store the computed value into the target key */
if (maxlen) {
robj *o = createObject(OBJ_STRING,res);
setKey(c->db,targetkey,o);
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
decrRefCount(o);
} else if (dbDelete(c->db,targetkey)) {
signalModifiedKey(c->db,targetkey);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
}
g_pserver->dirty++;
addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */
}
/* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */
void bitopCommand(client *c) {
char *opname = szFromObj(c->argv[1]);
robj *targetkey = c->argv[2];
robj_roptr o;
unsigned long op, numkeys;
int op;
unsigned long j, numkeys;
robj_roptr *objects; /* Array of source objects. */
unsigned char **src; /* Array of source strings pointers. */
unsigned long *len, maxlen = 0; /* Array of length of src strings,
and max len. */
unsigned long minlen = 0; /* Min len among the input keys. */
unsigned char *res = NULL; /* Resulting string. */
/* Parse the operation name. */
op = bitopFromStr(opname);
if (op == BITOP_INVALID) {
if ((opname[0] == 'a' || opname[0] == 'A') && !strcasecmp(opname,"and"))
op = BITOP_AND;
else if((opname[0] == 'o' || opname[0] == 'O') && !strcasecmp(opname,"or"))
op = BITOP_OR;
else if((opname[0] == 'x' || opname[0] == 'X') && !strcasecmp(opname,"xor"))
op = BITOP_XOR;
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
op = BITOP_NOT;
else if (!strcasecmp(opname, "lshift"))
op = BITOP_LSHIFT;
else if (!strcasecmp(opname, "rshift"))
op = BITOP_RSHIFT;
else {
addReply(c,shared.syntaxerr);
return;
}
@@ -869,13 +627,29 @@ void bitopCommand(client *c) {
return;
}
bool fShiftOp = (op == BITOP_LSHIFT) || (op == BITOP_RSHIFT);
long long shift = 0;
/* Sanity check: SHIFTS only accept a single arg and an integer */
if (fShiftOp) {
if (c->argc != 5) {
addReplyError(c,"BITOP SHIFT must be called with a single source key and an integer shift.");
return;
}
if (getLongLongFromObject(c->argv[4], &shift) != C_OK) {
addReplyError(c, "BITOP SHIFT's last parameter must be an integer");
return;
}
if (op == BITOP_RSHIFT)
shift = -shift;
}
/* Lookup keys, and store pointers to the string objects into an array. */
numkeys = c->argc - 3;
numkeys = c->argc - (fShiftOp ? 4 : 3);
src = (unsigned char**)zmalloc(sizeof(unsigned char*) * numkeys, MALLOC_LOCAL);
len = (unsigned long*)zmalloc(sizeof(long) * numkeys, MALLOC_LOCAL);
objects = (robj_roptr*)zmalloc(sizeof(robj_roptr) * numkeys, MALLOC_LOCAL);
for (unsigned long j = 0; j < numkeys; j++) {
for (j = 0; j < numkeys; j++) {
o = lookupKeyRead(c->db,c->argv[j+3]);
/* Handle non-existing keys as empty strings. */
if (o == nullptr) {
@@ -904,9 +678,146 @@ void bitopCommand(client *c) {
if (j == 0 || len[j] < minlen) minlen = len[j];
}
unsigned char *res = bitopCore(op, minlen, maxlen, numkeys, src, len);
for (unsigned long j = 0; j < numkeys; j++) {
if (fShiftOp)
{
long newlen = (long)maxlen + shift/CHAR_BIT;
if (shift > 0 && (shift % CHAR_BIT) != 0)
newlen++;
if (newlen < 0)
newlen = 0;
if (newlen)
{
res = (unsigned char*) sdsnewlen(NULL,newlen);
if (shift >= 0)
{ // left shift
long byteoffset = shift/CHAR_BIT;
memset(res, 0, byteoffset);
long srcLen = newlen - byteoffset - ((shift % CHAR_BIT) ? 1 : 0);
// now the bitshift+copy
unsigned bitshift = shift % CHAR_BIT;
unsigned char carry = 0;
for (long iSrc = 0; iSrc < srcLen; ++iSrc)
{
res[byteoffset+iSrc] = (src[0][iSrc] << bitshift) | carry;
carry = src[0][iSrc] >> (CHAR_BIT - bitshift);
}
if (bitshift)
res[newlen-1] = carry;
}
else
{ // right shift
long byteoffset = -shift/CHAR_BIT;
unsigned bitshift = -shift % CHAR_BIT;
if (bitshift)
++byteoffset;
res[0] = (src[0][byteoffset] << (CHAR_BIT-bitshift));
if (byteoffset > 0)
res[0] |= (src[0][byteoffset-1] >> bitshift);
for (long idx = 1; idx < newlen; ++idx)
{
res[idx] = (src[0][byteoffset+idx] << (CHAR_BIT-bitshift)) | (src[0][byteoffset+idx-1] >> bitshift);
}
}
}
maxlen = newlen; // this is to ensure we DEL below if newlen was 0
}
else
{
/* Compute the bit operation, if at least one string is not empty. */
if (maxlen) {
res = (unsigned char*) sdsnewlen(NULL,maxlen);
unsigned char output, byte;
unsigned long i;
/* Fast path: as far as we have data for all the input bitmaps we
* can take a fast path that performs much better than the
* vanilla algorithm. On ARM we skip the fast path since it will
* result in GCC compiling the code using multiple-words load/store
* operations that are not supported even in ARM >= v6. */
j = 0;
#ifndef USE_ALIGNED_ACCESS
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
unsigned long *lp[16];
unsigned long *lres = (unsigned long*) res;
/* Note: sds pointer is always aligned to 8 byte boundary. */
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
memcpy(res,src[0],minlen);
/* Different branches per different operations for speed (sorry). */
if (op == BITOP_AND) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] &= lp[i][0];
lres[1] &= lp[i][1];
lres[2] &= lp[i][2];
lres[3] &= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_OR) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] |= lp[i][0];
lres[1] |= lp[i][1];
lres[2] |= lp[i][2];
lres[3] |= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_XOR) {
while(minlen >= sizeof(unsigned long)*4) {
for (i = 1; i < numkeys; i++) {
lres[0] ^= lp[i][0];
lres[1] ^= lp[i][1];
lres[2] ^= lp[i][2];
lres[3] ^= lp[i][3];
lp[i]+=4;
}
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
} else if (op == BITOP_NOT) {
while(minlen >= sizeof(unsigned long)*4) {
lres[0] = ~lres[0];
lres[1] = ~lres[1];
lres[2] = ~lres[2];
lres[3] = ~lres[3];
lres+=4;
j += sizeof(unsigned long)*4;
minlen -= sizeof(unsigned long)*4;
}
}
}
#endif
/* j is set to the next byte to process by the previous loop. */
for (; j < maxlen; j++) {
output = (len[0] <= j) ? 0 : src[0][j];
if (op == BITOP_NOT) output = ~output;
for (i = 1; i < numkeys; i++) {
byte = (len[i] <= j) ? 0 : src[i][j];
switch(op) {
case BITOP_AND: output &= byte; break;
case BITOP_OR: output |= byte; break;
case BITOP_XOR: output ^= byte; break;
}
}
res[j] = output;
}
}
}
for (j = 0; j < numkeys; j++) {
if (objects[j])
decrRefCount(objects[j]);
}
+2 -2
View File
@@ -221,8 +221,8 @@ void replyToBlockedClientTimedOut(client *c) {
/* Mass-unblock clients because something changed in the instance that makes
* blocking no longer safe. For example clients blocked in list operations
* in an instance which turns from master to slave is unsafe, so this function
* is called when a master turns into a slave.
* in an instance which turns from master to replica is unsafe, so this function
* is called when a master turns into a replica.
*
* The semantics is to send an -UNBLOCKED error to the client, disconnecting
* it at the same time. */
+98 -43
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:
@@ -499,7 +508,7 @@ void clusterInit(void) {
}
if (listenToPort(g_pserver->port+CLUSTER_PORT_INCR,
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/) == C_ERR)
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR)
{
exit(1);
} else {
@@ -617,6 +626,18 @@ clusterLink *createClusterLink(clusterNode *node) {
* This function will just make sure that the original node associated
* with this link will have the 'link' field set to NULL. */
void freeClusterLink(clusterLink *link) {
if (ielFromEventLoop(serverTL->el) != IDX_EVENT_LOOP_MAIN)
{
// we can't perform this operation on this thread, queue it on the main thread
if (link->node)
link->node->link = NULL;
link->node = nullptr;
int res = aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [link]{
freeClusterLink(link);
});
serverAssert(res == AE_OK);
return;
}
if (link->fd != -1) {
aeDeleteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, link->fd, AE_READABLE|AE_WRITABLE);
}
@@ -2139,21 +2160,35 @@ void handleLinkIOError(clusterLink *link) {
* consumed by write(). We don't try to optimize this for speed too much
* as this is a very low traffic channel. */
void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
serverAssert(ielFromEventLoop(el) == IDX_EVENT_LOOP_MAIN);
clusterLink *link = (clusterLink*) privdata;
ssize_t nwritten;
UNUSED(el);
UNUSED(mask);
nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf));
// We're about to release the lock, so the link's sndbuf needs to be owned fully by us
// allocate a new one in case anyone tries to write while we're waiting
sds sndbuf = link->sndbuf;
link->sndbuf = sdsempty();
aeReleaseLock();
nwritten = write(fd, sndbuf, sdslen(sndbuf));
aeAcquireLock();
if (nwritten <= 0) {
serverLog(LL_DEBUG,"I/O error writing to node link: %s",
(nwritten == -1) ? strerror(errno) : "short write");
sdsfree(sndbuf);
handleLinkIOError(link);
return;
}
sdsrange(link->sndbuf,nwritten,-1);
sdsrange(sndbuf,nwritten,-1);
// Restore our send buffer, ensuring any unsent data is first
sndbuf = sdscat(sndbuf, link->sndbuf);
sdsfree(link->sndbuf);
link->sndbuf = sndbuf;
if (sdslen(link->sndbuf) == 0)
aeDeleteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, link->fd, AE_WRITABLE);
aeDeleteFileEvent(el, link->fd, AE_WRITABLE);
}
/* Read data. Try to read the first field of the header first to check the
@@ -2228,9 +2263,10 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
* the link to be invalidated, so it is safe to call this function
* from event handlers that will do stuff with the same link later. */
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
serverAssert(GlobalLocksAcquired());
if (sdslen(link->sndbuf) == 0 && msglen != 0)
aeCreateFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,link->fd,AE_WRITABLE|AE_BARRIER,
clusterWriteHandler,link);
aeCreateRemoteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,link->fd,AE_WRITABLE|AE_BARRIER,
clusterWriteHandler,link,false);
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
@@ -3284,7 +3320,7 @@ void clusterHandleSlaveMigration(int max_slaves) {
void resetManualFailover(void) {
if (g_pserver->cluster->mf_end && clientsArePaused()) {
g_pserver->clients_pause_end_time = 0;
clientsArePaused(); /* Just use the side effect of the function. */
unpauseClientsIfNecessary();
}
g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */
g_pserver->cluster->mf_can_start = 0;
@@ -4154,57 +4190,76 @@ void clusterReplyMultiBulkSlots(client *c) {
dictIterator *di = dictGetSafeIterator(g_pserver->cluster->nodes);
while((de = dictNext(di)) != NULL) {
clusterNode *node = (clusterNode*)dictGetVal(de);
int j = 0, start = -1;
int start = -1;
/* Skip slaves (that are iterated when producing the output of their
* master) and masters not serving any slot. */
if (!nodeIsMaster(node) || node->numslots == 0) continue;
static_assert((CLUSTER_SLOTS % (sizeof(uint32_t)*8)) == 0, "code below assumes the bitfield is a multiple of sizeof(unsinged)");
for (j = 0; j < CLUSTER_SLOTS; j++) {
int bit, i;
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {
if (start == -1) start = j;
for (unsigned iw = 0; iw < (CLUSTER_SLOTS/sizeof(uint32_t)/8); ++iw)
{
uint32_t wordCur = reinterpret_cast<uint32_t*>(node->slots)[iw];
if (iw != ((CLUSTER_SLOTS/sizeof(uint32_t)/8)-1))
{
if (start == -1 && wordCur == 0)
continue;
if (start != -1 && (wordCur+1)==0)
continue;
}
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
int nested_elements = 3; /* slots (2) + master addr (1). */
void *nested_replylen = addReplyDeferredLen(c);
if (bit && j == CLUSTER_SLOTS-1) j++;
/* If slot exists in output map, add to it's list.
* else, create a new output map for this slot */
if (start == j-1) {
addReplyLongLong(c, start); /* only one slot; low==high */
addReplyLongLong(c, start);
} else {
addReplyLongLong(c, start); /* low */
addReplyLongLong(c, j-1); /* high */
unsigned ibitStartLoop = iw*sizeof(uint32_t)*8;
for (unsigned j = ibitStartLoop; j < (iw+1)*sizeof(uint32_t)*8; j++) {
int i;
int bit = (int)(wordCur & 1);
wordCur >>= 1;
if (bit != 0) {
if (start == -1) start = j;
}
start = -1;
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
int nested_elements = 3; /* slots (2) + master addr (1). */
void *nested_replylen = addReplyDeferredLen(c);
/* First node reply position is always the master */
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->ip);
addReplyLongLong(c, node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
if (bit && j == CLUSTER_SLOTS-1) j++;
/* Remaining nodes in reply are replicas for slot range */
for (i = 0; i < node->numslaves; i++) {
/* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation */
if (nodeFailed(node->slaves[i])) continue;
/* If slot exists in output map, add to it's list.
* else, create a new output map for this slot */
if (start == j-1) {
addReplyLongLong(c, start); /* only one slot; low==high */
addReplyLongLong(c, start);
} else {
addReplyLongLong(c, start); /* low */
addReplyLongLong(c, j-1); /* high */
}
start = -1;
/* First node reply position is always the master */
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++;
addReplyBulkCString(c, node->ip);
addReplyLongLong(c, node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
/* Remaining nodes in reply are replicas for slot range */
for (i = 0; i < node->numslaves; i++) {
/* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation */
if (nodeFailed(node->slaves[i])) continue;
addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++;
}
setDeferredArrayLen(c, nested_replylen, nested_elements);
num_masters++;
}
setDeferredArrayLen(c, nested_replylen, nested_elements);
num_masters++;
}
}
serverAssert(start == -1);
}
dictReleaseIterator(di);
setDeferredArrayLen(c, slot_replylen, num_masters);
}
+21 -4
View File
@@ -96,7 +96,7 @@ configEnum aof_fsync_enum[] = {
/* Output buffer limits presets. */
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
{0, 0, 0}, /* normal */
{1024*1024*256, 1024*1024*64, 60}, /* slave */
{1024*1024*256, 1024*1024*64, 60}, /* replica */
{1024*1024*32, 1024*1024*8, 60} /* pubsub */
};
@@ -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)
@@ -800,6 +800,16 @@ void loadServerConfigFromString(char *config) {
serverLog(LL_WARNING, "Warning version is overriden to: %s\n", KEYDB_SET_VERSION);
} else if (!strcasecmp(argv[0],"testmode") && argc == 2){
g_fTestMode = yesnotoi(argv[1]);
} else if (!strcasecmp(argv[0],"rdbfuzz-mode")) {
// NOP, handled in main
} else 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;
}
@@ -1486,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);
@@ -1725,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;
@@ -1888,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);
+58 -14
View File
@@ -30,6 +30,7 @@
#include "server.h"
#include "cluster.h"
#include "atomicvar.h"
#include "aelocker.h"
#include <signal.h>
#include <ctype.h>
@@ -114,7 +115,7 @@ static robj *lookupKey(redisDb *db, robj *key, int flags) {
* LOOKUP_NOTOUCH: don't alter the last access time of the key.
*
* Note: this function also returns NULL if the key is logically expired
* but still existing, in case this is a slave, since this API is called only
* but still existing, in case this is a replica, since this API is called only
* for read operations. Even if the key expiry is master-driven, we can
* correctly report a key is expired on slaves even if the master is lagging
* expiring our key via DELs in the replication link. */
@@ -132,7 +133,7 @@ robj_roptr lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
return NULL;
}
/* However if we are in the context of a slave, expireIfNeeded() will
/* However if we are in the context of a replica, expireIfNeeded() will
* not really try to expire the key, it only returns information
* about the "logical" status of the key: key expiring is up to the
* master in order to have a consistent view of master's data set.
@@ -176,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;
}
@@ -343,7 +343,7 @@ robj *dbRandomKey(redisDb *db) {
if (allvolatile && listLength(g_pserver->masters) && --maxtries == 0) {
/* If the DB is composed only of keys with an expire set,
* it could happen that all the keys are already logically
* expired in the slave, so the function cannot stop because
* expired in the replica, so the function cannot stop because
* expireIfNeeded() is false, nor it can stop because
* dictGetRandomKey() returns NULL (there are keys to return).
* To prevent the infinite loop we do some tries, but if there
@@ -977,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.");
}
@@ -1132,7 +1132,8 @@ int dbSwapDatabases(int id1, int id2) {
if (id1 < 0 || id1 >= cserver.dbnum ||
id2 < 0 || id2 >= cserver.dbnum) return C_ERR;
if (id1 == id2) return C_OK;
redisDb aux = g_pserver->db[id1];
redisDb aux;
memcpy(&aux, &g_pserver->db[id1], sizeof(redisDb));
redisDb *db1 = &g_pserver->db[id1], *db2 = &g_pserver->db[id2];
/* Swap hash tables. Note that we don't swap blocking_keys,
@@ -1217,6 +1218,39 @@ int removeExpireCore(redisDb *db, robj *key, dictEntry *de) {
return 1;
}
int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey) {
dictEntry *de = dictFind(db->pdict,ptrFromObj(key));
serverAssertWithInfo(NULL,key,de != NULL);
robj *val = (robj*)dictGetVal(de);
if (!val->FExpires())
return 0;
auto itr = db->setexpire->find((sds)dictGetKey(de));
serverAssert(itr != db->setexpire->end());
serverAssert(itr->key() == (sds)dictGetKey(de));
if (!itr->FFat())
return 0;
int found = 0;
for (auto subitr : *itr)
{
if (subitr.subkey() == nullptr)
continue;
if (sdscmp((sds)subitr.subkey(), szFromObj(subkey)) == 0)
{
itr->erase(subitr);
found = 1;
break;
}
}
if (itr->pfatentry()->size() == 0)
removeExpireCore(db, key, de);
return found;
}
/* Set an expire to the specified key. If the expire is set in the context
* of an user calling a command 'c' is the client, otherwise 'c' is set
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
@@ -1271,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;
@@ -1325,7 +1367,7 @@ expireEntry *getExpire(redisDb *db, robj_roptr key) {
* to all the slaves and the AOF file if enabled.
*
* This way the key expiry is centralized in one place, and since both
* AOF and the master->slave link guarantee operation ordering, everything
* AOF and the master->replica link guarantee operation ordering, everything
* will be consistent even if we allow write operations against expiring
* keys. */
void propagateExpire(redisDb *db, robj *key, int lazy) {
@@ -1339,7 +1381,9 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
if (g_pserver->aof_state != AOF_OFF)
feedAppendOnlyFile(cserver.delCommand,db->id,argv,2);
replicationFeedSlaves(g_pserver->slaves,db->id,argv,2);
// Active replicas do their own expiries, do not propogate
if (!g_pserver->fActiveReplica)
replicationFeedSlaves(g_pserver->slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
@@ -1383,10 +1427,10 @@ int keyIsExpired(redisDb *db, robj *key) {
* is via lookupKey*() family of functions.
*
* The behavior of the function depends on the replication role of the
* instance, because slave instances do not expire keys, they wait
* instance, because replica instances do not expire keys, they wait
* for DELs from the master for consistency matters. However even
* slaves will try to have a coherent return value for the function,
* so that read commands executed in the slave side will be able to
* so that read commands executed in the replica side will be able to
* behave like if the key is expired even if still present (because the
* master has yet to propagate the DEL).
*
@@ -1399,15 +1443,15 @@ int keyIsExpired(redisDb *db, robj *key) {
int expireIfNeeded(redisDb *db, robj *key) {
if (!keyIsExpired(db,key)) return 0;
/* If we are running in the context of a slave, instead of
/* If we are running in the context of a replica, instead of
* evicting the expired key from the database, we return ASAP:
* the slave key expiration is controlled by the master that will
* the replica key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (listLength(g_pserver->masters)) return 1;
if (listLength(g_pserver->masters) && !g_pserver->fActiveReplica) return 1;
/* Delete the key */
g_pserver->stat_expiredkeys++;
+23 -3
View File
@@ -55,7 +55,7 @@ typedef ucontext_t sigcontext_t;
#endif
#endif
bool g_fInCrash = false;
int g_fInCrash = false;
/* ================================= Debugging ============================== */
@@ -682,10 +682,30 @@ NULL
changeReplicationId();
clearReplicationId2();
addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2)
{
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2) {
stringmatchlen_fuzz_test();
addReplyStatus(c,"Apparently Redis did not crash: test passed");
} else if (!strcasecmp(szFromObj(c->argv[1]), "force-master") && c->argc == 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);
return;
+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;
}
+2 -2
View File
@@ -374,8 +374,8 @@ size_t freeMemoryGetNotCountedMemory(void) {
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *slave = (client*)listNodeValue(ln);
overhead += getClientOutputBufferMemoryUsage(slave);
client *replica = (client*)listNodeValue(ln);
overhead += getClientOutputBufferMemoryUsage(replica);
}
}
if (g_pserver->aof_state != AOF_OFF) {
+157 -32
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)
{
@@ -130,28 +163,59 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
}
}
void expireMemberCommand(client *c)
int parseUnitString(const char *sz)
{
long long when;
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
return;
if (strcasecmp(sz, "s") == 0)
return UNIT_SECONDS;
if (strcasecmp(sz, "ms") == 0)
return UNIT_MILLISECONDS;
return -1;
}
when *= 1000;
when += mstime();
void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, long long when, int unit)
{
switch (unit)
{
case UNIT_SECONDS:
when *= 1000;
case UNIT_MILLISECONDS:
break;
default:
addReplyError(c, "Invalid unit arg");
return;
}
when += basetime;
/* No key, return zero. */
dictEntry *de = dictFind(c->db->pdict, szFromObj(c->argv[1]));
if (de == NULL) {
addReply(c,shared.czero);
robj *val = lookupKeyWriteOrReply(c, key, shared.czero);
if (val == NULL) {
return;
}
robj *val = (robj*)dictGetVal(de);
double dblT;
switch (val->type)
{
case OBJ_SET:
// these types are safe
if (!setTypeIsMember(val, szFromObj(subkey))) {
addReply(c,shared.czero);
return;
}
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:
@@ -159,11 +223,40 @@ void expireMemberCommand(client *c)
return;
}
setExpire(c, c->db, c->argv[1], c->argv[2], when);
setExpire(c, c->db, key, subkey, when);
addReply(c, shared.ok);
addReply(c, shared.cone);
}
void expireMemberCommand(client *c)
{
long long when;
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
return;
if (c->argc > 5) {
addReplyError(c, "Invalid number of arguments");
return;
}
int unit = UNIT_SECONDS;
if (c->argc == 5) {
unit = parseUnitString(szFromObj(c->argv[4]));
}
expireMemberCore(c, c->argv[1], c->argv[2], mstime(), when, unit);
}
void expireMemberAtCommand(client *c)
{
long long when;
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
return;
expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_SECONDS);
}
/* Try to expire a few timed out keys. The algorithm used is adaptive and
* will use few CPU cycles if there are few expiring keys, otherwise
* it will get more aggressive to avoid that too much memory is used by
@@ -307,17 +400,17 @@ void activeExpireCycle(int type) {
*
* Normally slaves do not process expires: they wait the masters to synthesize
* DEL operations in order to retain consistency. However writable slaves are
* an exception: if a key is created in the slave and an expire is assigned
* an exception: if a key is created in the replica and an expire is assigned
* to it, we need a way to expire such a key, since the master does not know
* anything about such a key.
*
* In order to do so, we track keys created in the slave side with an expire
* In order to do so, we track keys created in the replica side with an expire
* set, and call the expireSlaveKeys() function from time to time in order to
* reclaim the keys if they already expired.
*
* Note that the use case we are trying to cover here, is a popular one where
* slaves are put in writable mode in order to compute slow operations in
* the slave side that are mostly useful to actually read data in a more
* the replica side that are mostly useful to actually read data in a more
* processed way. Think at sets intersections in a tmp key, with an expire so
* that it is also used as a cache to avoid intersecting every time.
*
@@ -326,7 +419,7 @@ void activeExpireCycle(int type) {
*----------------------------------------------------------------------------*/
/* The dictionary where we remember key names and database ID of keys we may
* want to expire from the slave. Since this function is not often used we
* want to expire from the replica. Since this function is not often used we
* don't even care to initialize the database at startup. We'll do it once
* the feature is used the first time, that is, when rememberSlaveKeyWithExpire()
* is called.
@@ -389,7 +482,7 @@ void expireSlaveKeys(void) {
}
/* Set the new bitmap as value of the key, in the dictionary
* of keys with an expire set directly in the writable slave. Otherwise
* of keys with an expire set directly in the writable replica. Otherwise
* if the bitmap is zero, we no longer need to keep track of it. */
if (new_dbids)
dictSetUnsignedIntegerVal(de,new_dbids);
@@ -406,7 +499,7 @@ void expireSlaveKeys(void) {
}
/* Track keys that received an EXPIRE or similar command in the context
* of a writable slave. */
* of a writable replica. */
void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
if (slaveKeysWithExpire == NULL) {
static dictType dt = {
@@ -448,7 +541,7 @@ size_t getSlaveKeyWithExpireCount(void) {
*
* Note: technically we should handle the case of a single DB being flushed
* but it is not worth it since anyway race conditions using the same set
* of key names in a wriatable slave and in its master will lead to
* of key names in a wriatable replica and in its master will lead to
* inconsistencies. This is just a best-effort thing we do. */
void flushSlaveKeysWithExpireList(void) {
if (slaveKeysWithExpire) {
@@ -486,11 +579,11 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
* should never be executed as a DEL when load the AOF or in the context
* of a slave instance.
* of a replica instance.
*
* Instead we take the other branch of the IF statement setting an expire
* (possibly in the past) and wait for an explicit DEL from the master. */
if (when <= mstime() && !g_pserver->loading && !listLength(g_pserver->masters)) {
if (when <= mstime() && !g_pserver->loading && (!listLength(g_pserver->masters) || g_pserver->fActiveReplica)) {
robj *aux;
int deleted = g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
@@ -544,11 +637,32 @@ void ttlGenericCommand(client *c, int output_ms) {
addReplyLongLong(c,-2);
return;
}
/* The key exists. Return -1 if it has no expire, or the actual
* TTL value otherwise. */
* TTL value otherwise. */
expireEntry *pexpire = getExpire(c->db,c->argv[1]);
if (pexpire != nullptr)
pexpire->FGetPrimaryExpire(&expire);
if (c->argc == 2) {
// primary expire
if (pexpire != nullptr)
pexpire->FGetPrimaryExpire(&expire);
} else if (c->argc == 3) {
// We want a subkey expire
if (pexpire && pexpire->FFat()) {
for (auto itr : *pexpire) {
if (itr.subkey() == nullptr)
continue;
if (sdscmp((sds)itr.subkey(), szFromObj(c->argv[2])) == 0) {
expire = itr.when();
break;
}
}
}
} else {
addReplyError(c, "Invalid arguments");
return;
}
if (expire != -1) {
ttl = expire-mstime();
@@ -574,11 +688,22 @@ void pttlCommand(client *c) {
/* PERSIST key */
void persistCommand(client *c) {
if (lookupKeyWrite(c->db,c->argv[1])) {
if (removeExpire(c->db,c->argv[1])) {
addReply(c,shared.cone);
g_pserver->dirty++;
if (c->argc == 2) {
if (removeExpire(c->db,c->argv[1])) {
addReply(c,shared.cone);
g_pserver->dirty++;
} else {
addReply(c,shared.czero);
}
} else if (c->argc == 3) {
if (removeSubkeyExpire(c->db, c->argv[1], c->argv[2])) {
addReply(c,shared.cone);
g_pserver->dirty++;
} else {
addReply(c,shared.czero);
}
} else {
addReply(c,shared.czero);
addReplyError(c, "Invalid arguments");
}
} else {
addReply(c,shared.czero);
+188 -55
View File
@@ -27,6 +27,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fmacros.h"
#include "fastlock.h"
#include <unistd.h>
#include <sys/syscall.h>
@@ -36,10 +37,14 @@
#include <assert.h>
#include <pthread.h>
#include <limits.h>
#include <map>
#ifdef __linux__
#include <linux/futex.h>
#endif
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include "config.h"
#ifdef __APPLE__
#include <TargetConditionals.h>
@@ -57,6 +62,12 @@
#define UNUSED(x) ((void)x)
#endif
#ifdef HAVE_BACKTRACE
#include <ucontext.h>
__attribute__((weak)) void logStackTrace(ucontext_t *) {}
#endif
extern int g_fInCrash;
/****************************************************
*
@@ -70,6 +81,10 @@
#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". */
@@ -125,26 +140,21 @@
#endif
static_assert(sizeof(pid_t) <= sizeof(fastlock::m_pidOwner), "fastlock::m_pidOwner not large enough");
uint64_t g_longwaits = 0;
uint64_t fastlock_getlongwaitcount()
#pragma weak _serverPanic
extern "C" __attribute__((weak)) void _serverPanic(const char * /*file*/, int /*line*/, const char * /*msg*/, ...)
{
uint64_t rval;
__atomic_load(&g_longwaits, &rval, __ATOMIC_RELAXED);
return rval;
*((char*)-1) = 'x';
}
#ifndef ASM_SPINLOCK
#ifdef __linux__
static int futex(volatile unsigned *uaddr, int futex_op, int val,
const struct timespec *timeout, int val3)
#pragma weak serverLog
__attribute__((weak)) void serverLog(int , const char *fmt, ...)
{
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr, val3);
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf("\n");
}
#endif
#endif
extern "C" pid_t gettid()
{
@@ -163,13 +173,145 @@ extern "C" pid_t gettid()
return pidCache;
}
extern "C" void fastlock_init(struct fastlock *lock)
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;
fastlock m_lock { "deadlock detector" };
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
pid_t pidCheck = thispid;
size_t cchecks = 0;
for (;;)
{
auto itr = m_mapwait.find(pidCheck);
if (itr == m_mapwait.end())
break;
pidCheck = itr->second->m_pidOwner;
if (pidCheck == thispid)
{
// Deadlock detected, printout some debugging info and crash
serverLog(3 /*LL_WARNING*/, "\n\n");
serverLog(3 /*LL_WARNING*/, "!!! ERROR: Deadlock detected !!!");
pidCheck = thispid;
for (;;)
{
auto itr = m_mapwait.find(pidCheck);
serverLog(3 /* LL_WARNING */, "\t%d: (%p) %s", pidCheck, itr->second, itr->second->szName);
pidCheck = itr->second->m_pidOwner;
if (pidCheck == thispid)
break;
}
// 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");
}
if (cchecks > m_mapwait.size())
break; // There is a cycle but we're not in it
++cchecks;
}
fastlock_unlock(&m_lock);
}
void clearwait(fastlock *lock, pid_t thispid)
{
if (lock == &m_lock || g_fInCrash)
return;
fastlock_lock(&m_lock);
m_mapwait.erase(thispid);
fastlock_unlock(&m_lock);
}
};
DeadlockDetector g_dlock;
static_assert(sizeof(pid_t) <= sizeof(fastlock::m_pidOwner), "fastlock::m_pidOwner not large enough");
uint64_t g_longwaits = 0;
extern "C" void fastlock_panic(struct fastlock *lock)
{
_serverPanic(__FILE__, __LINE__, "fastlock lock/unlock mismatch for: %s", lock->szName);
}
uint64_t fastlock_getlongwaitcount()
{
uint64_t rval;
__atomic_load(&g_longwaits, &rval, __ATOMIC_RELAXED);
return rval;
}
extern "C" void fastlock_sleep(fastlock *lock, pid_t pid, unsigned wake, unsigned mask)
{
#ifdef __linux__
g_dlock.registerwait(lock, pid);
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, wake, nullptr, mask);
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
g_dlock.clearwait(lock, pid);
#endif
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
}
extern "C" void fastlock_init(struct fastlock *lock, const char *name)
{
lock->m_ticket.m_active = 0;
lock->m_ticket.m_avail = 0;
lock->m_depth = 0;
lock->m_pidOwner = -1;
lock->futex = 0;
int cch = strlen(name);
cch = std::min<int>(cch, sizeof(lock->szName)-1);
memcpy(lock->szName, name, cch);
lock->szName[cch] = '\0';
ANNOTATE_RWLOCK_CREATE(lock);
}
@@ -184,12 +326,12 @@ extern "C" void fastlock_lock(struct fastlock *lock)
return;
}
int tid = gettid();
unsigned myticket = __atomic_fetch_add(&lock->m_ticket.m_avail, 1, __ATOMIC_RELEASE);
#ifdef __linux__
unsigned mask = (1U << (myticket % 32));
#endif
int cloops = 0;
unsigned cloops = 0;
ticket ticketT;
for (;;)
{
__atomic_load(&lock->m_ticket.u, &ticketT.u, __ATOMIC_ACQUIRE);
@@ -197,21 +339,17 @@ 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)
{
#ifdef __linux__
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, ticketT.u, nullptr, mask);
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
#endif
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
fastlock_sleep(lock, tid, ticketT.u, mask);
}
}
lock->m_depth = 1;
int tid = gettid();
__atomic_store(&lock->m_pidOwner, &tid, __ATOMIC_RELEASE);
ANNOTATE_RWLOCK_ACQUIRED(lock, true);
std::atomic_thread_fence(std::memory_order_acquire);
@@ -238,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();
@@ -249,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;
@@ -296,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
@@ -325,4 +458,4 @@ void fastlock_lock_recursive(struct fastlock *lock, int nesting)
{
fastlock_lock(lock);
lock->m_depth = nesting;
}
}
+13 -8
View File
@@ -1,5 +1,6 @@
#pragma once
#include <inttypes.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
@@ -7,7 +8,7 @@ extern "C" {
/* Begin C API */
struct fastlock;
void fastlock_init(struct fastlock *lock);
void fastlock_init(struct fastlock *lock, const char *name);
void fastlock_lock(struct fastlock *lock);
int fastlock_trylock(struct fastlock *lock, int fWeak);
void fastlock_unlock(struct fastlock *lock);
@@ -40,29 +41,31 @@ struct ticket
struct fastlock
{
volatile struct ticket m_ticket;
volatile int m_pidOwner;
volatile int m_depth;
char szName[56];
/* Volatile data on seperate cache line */
volatile struct ticket m_ticket;
unsigned futex;
char padding[56]; // ensure ticket and futex are on their own independent cache line
#ifdef __cplusplus
fastlock()
fastlock(const char *name)
{
fastlock_init(this);
fastlock_init(this, name);
}
void lock()
inline void lock()
{
fastlock_lock(this);
}
bool try_lock(bool fWeak = false)
inline bool try_lock(bool fWeak = false)
{
return !!fastlock_trylock(this, fWeak);
}
void unlock()
inline void unlock()
{
fastlock_unlock(this);
}
@@ -80,3 +83,5 @@ struct fastlock
bool fOwnLock(); // true if this thread owns the lock, NOTE: not 100% reliable, use for debugging only
#endif
};
static_assert(offsetof(struct fastlock, m_ticket) == 64, "ensure padding is correct");
+120 -125
View File
@@ -1,157 +1,152 @@
section .text
.intel_syntax noprefix
.text
extern gettid
extern sched_yield
extern g_longwaits
.extern gettid
.extern fastlock_sleep
; This is the first use of assembly in this codebase, a valid question is WHY?
; The spinlock we implement here is performance critical, and simply put GCC
; emits awful code. The original C code is left in fastlock.cpp for reference
; and x-plat.
# This is the first use of assembly in this codebase, a valid question is WHY?
# The spinlock we implement here is performance critical, and simply put GCC
# emits awful code. The original C code is left in fastlock.cpp for reference
# and x-plat.
ALIGN 16
global fastlock_lock
.ALIGN 16
.global fastlock_lock
.type fastlock_lock,@function
fastlock_lock:
; RDI points to the struct:
; uint16_t active
; uint16_t avail
; int32_t m_pidOwner
; int32_t m_depth
.cfi_startproc
.cfi_def_cfa rsp, 8
# RDI points to the struct:
# int32_t m_pidOwner
# int32_t m_depth
# [rdi+64] ...
# uint16_t active
# uint16_t avail
; First get our TID and put it in ecx
push rdi ; we need our struct pointer (also balance the stack for the call)
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax ; back it up in esi
pop rdi ; get our pointer back
# First get our TID and put it in ecx
push rdi # we need our struct pointer (also balance the stack for the call)
.cfi_adjust_cfa_offset 8
call gettid # get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax # back it up in esi
pop rdi # get our pointer back
.cfi_adjust_cfa_offset -8
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
je .LLocked ; Don't spin in that case
cmp [rdi], esi # Is the TID we got back the owner of the lock?
je .LLocked # Don't spin in that case
xor eax, eax ; eliminate partial register dependency
inc eax ; we want to add one
lock xadd [rdi+2], ax ; do the xadd, ax contains the value before the addition
; ax now contains the ticket
ALIGN 16
xor eax, eax # eliminate partial register dependency
inc eax # we want to add one
lock xadd [rdi+66], ax # do the xadd, ax contains the value before the addition
# ax now contains the ticket
# OK Start the wait loop
xor ecx, ecx
.ALIGN 16
.LLoop:
mov edx, [rdi]
cmp dx, ax ; is our ticket up?
je .LLocked ; leave the loop
mov edx, [rdi+64]
cmp dx, ax # is our ticket up?
je .LLocked # leave the loop
pause
add ecx, 1000h ; Have we been waiting a long time? (oflow if we have)
; 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
jnc .LLoop ; If so, give up our timeslice to someone who's doing real work
; Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
; But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
; taking a long time to be released anyways. We optimize for the common case of short
; lock intervals. That's why we're using a spinlock in the first place
; If we get here we're going to sleep in the kernel with a futex
add ecx, 0x1000 # Have we been waiting a long time? (oflow if we have)
# 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
jnc .LLoop # If so, give up our timeslice to someone who's doing real work
# Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
# But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
# taking a long time to be released anyways. We optimize for the common case of short
# lock intervals. That's why we're using a spinlock in the first place
# If we get here we're going to sleep in the kernel with a futex
push rdi
push rsi
push rax
; Setup the syscall args
; rdi ARG1 futex (already in rdi)
mov esi, (9 | 128) ; rsi ARG2 FUTEX_WAIT_BITSET_PRIVATE
; rdx ARG3 ticketT.u (already in edx)
xor r10d, r10d ; r10 ARG4 NULL
mov r8, rdi ; r8 ARG5 dup rdi
xor r9d, r9d
bts r9d, eax ; r9 ARG6 mask
mov eax, 202 ; sys_futex
; Do the syscall
lock or [rdi+12], r9d ; inform the unlocking thread we're waiting
syscall ; wait for the futex
not r9d ; convert our flag into a mask of bits not to touch
lock and [rdi+12], r9d ; clear the flag in the futex control mask
; cleanup and continue
mov rcx, g_longwaits
inc qword [rcx] ; increment our long wait counter
.cfi_adjust_cfa_offset 24
# Setup the syscall args
# rdi ARG1 futex (already in rdi)
# rsi ARG2 tid (already in esi)
# rdx ARG3 ticketT.u (already in edx)
bts ecx, eax # rcx ARG4 mask
call fastlock_sleep
# cleanup and continue
pop rax
pop rsi
xor ecx, ecx ; Reset our loop counter
jmp .LLoop ; Get back in the game
ALIGN 16
pop rdi
.cfi_adjust_cfa_offset -24
xor ecx, ecx # Reset our loop counter
jmp .LLoop # Get back in the game
.ALIGN 16
.LLocked:
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
inc dword [rdi+8] ; lock->m_depth++
mov [rdi], esi # lock->m_pidOwner = gettid()
inc dword ptr [rdi+4] # lock->m_depth++
ret
.cfi_endproc
ALIGN 16
global fastlock_trylock
.ALIGN 16
.global fastlock_trylock
.type fastlock_trylock,@function
fastlock_trylock:
; RDI points to the struct:
; uint16_t active
; uint16_t avail
; int32_t m_pidOwner
; int32_t m_depth
# RDI points to the struct:
# int32_t m_pidOwner
# int32_t m_depth
# [rdi+64] ...
# uint16_t active
# uint16_t avail
; First get our TID and put it in ecx
push rdi ; we need our struct pointer (also balance the stack for the call)
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax ; back it up in esi
pop rdi ; get our pointer back
# First get our TID and put it in ecx
push rdi # we need our struct pointer (also balance the stack for the call)
call gettid # get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax # back it up in esi
pop rdi # get our pointer back
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
je .LRecursive ; Don't spin in that case
cmp [rdi], esi # Is the TID we got back the owner of the lock?
je .LRecursive # Don't spin in that case
mov eax, [rdi] ; get both active and avail counters
mov ecx, eax ; duplicate in ecx
ror ecx, 16 ; swap upper and lower 16-bits
cmp eax, ecx ; are the upper and lower 16-bits the same?
jnz .LAlreadyLocked ; If not return failure
mov eax, [rdi+64] # get both active and avail counters
mov ecx, eax # duplicate in ecx
ror ecx, 16 # swap upper and lower 16-bits
cmp eax, ecx # are the upper and lower 16-bits the same?
jnz .LAlreadyLocked # If not return failure
; at this point we know eax+ecx have [avail][active] and they are both the same
add ecx, 10000h ; increment avail, ecx is now our wanted value
lock cmpxchg [rdi], ecx ; If rdi still contains the value in eax, put in ecx (inc avail)
jnz .LAlreadyLocked ; If Z is not set then someone locked it while we were preparing
# at this point we know eax+ecx have [avail][active] and they are both the same
add ecx, 0x10000 # increment avail, ecx is now our wanted value
lock cmpxchg [rdi+64], ecx # If rdi still contains the value in eax, put in ecx (inc avail)
jnz .LAlreadyLocked # If Z is not set then someone locked it while we were preparing
xor eax, eax
inc eax ; return SUCCESS! (eax=1)
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
mov dword [rdi+8], eax ; lock->m_depth = 1
inc eax # return SUCCESS! (eax=1)
mov [rdi], esi # lock->m_pidOwner = gettid()
mov dword ptr [rdi+4], eax # lock->m_depth = 1
ret
ALIGN 16
.ALIGN 16
.LRecursive:
xor eax, eax
inc eax ; return SUCCESS! (eax=1)
inc dword [rdi+8] ; lock->m_depth++
inc eax # return SUCCESS! (eax=1)
inc dword ptr [rdi+4] # lock->m_depth++
ret
ALIGN 16
.ALIGN 16
.LAlreadyLocked:
xor eax, eax ; return 0;
xor eax, eax # return 0
ret
ALIGN 16
global fastlock_unlock
.ALIGN 16
.global fastlock_unlock
.type fastlock_unlock,@function
fastlock_unlock:
; RDI points to the struct:
; uint16_t active
; uint16_t avail
; int32_t m_pidOwner
; int32_t m_depth
push r11
sub dword [rdi+8], 1 ; decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
jnz .LDone ; if depth is non-zero this is a recursive unlock, and we still hold it
mov dword [rdi+4], -1 ; pidOwner = -1 (we don't own it anymore)
mov ecx, [rdi] ; get current active (this one)
inc ecx ; bump it to the next thread
mov [rdi], cx ; give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
; At this point the lock is removed, however we must wake up any pending futexs
mov r9d, 1 ; eax is the bitmask for 2 threads
rol r9d, cl ; place the mask in the right spot for the next 2 threads
ALIGN 16
.LRetryWake:
mov r11d, [rdi+12] ; load the futex mask
and r11d, r9d ; are any threads waiting on a futex?
jz .LDone ; if not we're done.
; we have to wake the futexs
; rdi ARG1 futex (already in rdi)
mov esi, (10 | 128) ; rsi ARG2 FUTEX_WAKE_BITSET_PRIVATE
mov edx, 0x7fffffff ; rdx ARG3 INT_MAX (number of threads to wake)
xor r10d, r10d ; r10 ARG4 NULL
mov r8, rdi ; r8 ARG5 dup rdi
; r9 ARG6 mask (already set above)
mov eax, 202 ; sys_futex
syscall
cmp eax, 1 ; did we wake as many as we expected?
jnz .LRetryWake
# RDI points to the struct:
# int32_t m_pidOwner
# int32_t m_depth
# [rdi+64] ...
# uint16_t active
# uint16_t avail
sub dword ptr [rdi+4], 1 # decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
jnz .LDone # if depth is non-zero this is a recursive unlock, and we still hold it
mov dword ptr [rdi], -1 # pidOwner = -1 (we don't own it anymore)
mov esi, [rdi+64] # get current active (this one)
inc esi # bump it to the next thread
mov word ptr [rdi+64], si # give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
mfence # sync other threads
# At this point the lock is removed, however we must wake up any pending futexs
mov edx, [rdi+64+4] # load the futex mask
bt edx, esi # is the next thread waiting on a futex?
jc unlock_futex # unlock the futex if necessary
ret # if not we're done.
.ALIGN 16
.LDone:
pop r11
js fastlock_panic # panic if we made m_depth negative
ret
+18 -14
View File
@@ -63,11 +63,6 @@ struct commandHelp {
"Perform bitwise operations between strings",
1,
"2.6.0" },
{ "BITOPRANGE",
"operation destkey key start:end [key ...]",
"Perform bitwise operations between strings with offset and range",
1,
"5.1.0" },
{ "BITPOS",
"key bit [start] [end]",
"Find first bit set or clear in a string",
@@ -349,8 +344,11 @@ struct commandHelp {
0,
"1.2.0" },
{ "EXPIREMEMBER",
"key subkey seconds",
"set a subkey's time to live in seconds"},
"key subkey delay [Unit: s,ms]",
"set a subkey's time to live in seconds (or milliseconds)"},
{ "EXPIREMEMBERAT",
"key subkey timestamp",
"Set the expiration for a subkey as a UNIX timestamp"},
{ "FLUSHALL",
"[ASYNC]",
"Remove all keys from all databases",
@@ -637,8 +635,8 @@ struct commandHelp {
0,
"2.2.3" },
{ "PERSIST",
"key",
"Remove the expiration from a key",
"key [subkey]",
"Remove the expiration from a key or subkey",
0,
"2.2.0" },
{ "PEXPIRE",
@@ -682,8 +680,8 @@ struct commandHelp {
6,
"2.0.0" },
{ "PTTL",
"key",
"Get the time to live for a key in milliseconds",
"key [subkey]",
"Get the time to live for a key or subkey in milliseconds",
0,
"2.6.0" },
{ "PUBLISH",
@@ -957,8 +955,8 @@ struct commandHelp {
0,
"3.2.1" },
{ "TTL",
"key",
"Get the time to live for a key",
"key [subkey]",
"Get the time to live for a key or subkey",
0,
"1.0.0" },
{ "TYPE",
@@ -1170,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
+5 -5
View File
@@ -1439,7 +1439,7 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
*
* * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master
*
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a replica
*
* * REDISMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only
*
@@ -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,
@@ -4289,7 +4289,7 @@ size_t RM_GetClusterSize(void) {
*
* The arguments ip, master_id, port and flags can be NULL in case we don't
* need to populate back certain info. If an ip and master_id (only populated
* if the instance is a slave) are specified, they point to buffers holding
* if the instance is a replica) are specified, they point to buffers holding
* at least REDISMODULE_NODE_ID_LEN bytes. The strings written back as ip
* and master_id are not null terminated.
*
@@ -4300,7 +4300,7 @@ size_t RM_GetClusterSize(void) {
* * REDISMODULE_NODE_SLAVE The node is a replica
* * REDISMODULE_NODE_PFAIL We see the node as failing
* * REDISMODULE_NODE_FAIL The cluster agrees the node is failing
* * REDISMODULE_NODE_NOFAILOVER The slave is configured to never failover
* * REDISMODULE_NODE_NOFAILOVER The replica is configured to never failover
*/
clusterNode *clusterLookupNode(const char *name); /* We need access to internals */
@@ -5181,7 +5181,7 @@ void moduleInitModulesSystem(void) {
* The function aborts the server on errors, since to start with missing
* modules is not considered sane: clients may rely on the existence of
* given commands, loading AOF also may need some modules to exist, and
* if this instance is a slave, it must understand commands from master. */
* if this instance is a replica, it must understand commands from master. */
void moduleLoadFromQueue(void) {
listIter li;
listNode *ln;
+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
+8 -4
View File
@@ -28,6 +28,7 @@
*/
#include "server.h"
bool FInReplicaReplay();
/* ================================ MULTI/EXEC ============================== */
@@ -142,7 +143,7 @@ void execCommand(client *c) {
}
/* If there are write commands inside the transaction, and this is a read
* only slave, we want to send an error. This happens when the transaction
* only replica, we want to send an error. This happens when the transaction
* was initiated when the instance was a master or a writable replica and
* then the configuration changed (for example instance was turned into
* a replica). */
@@ -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;
@@ -195,7 +199,7 @@ void execCommand(client *c) {
int is_master = listLength(g_pserver->masters) == 0;
g_pserver->dirty++;
/* If inside the MULTI/EXEC block this instance was suddenly
* switched from master to slave (using the SLAVEOF command), the
* switched from master to replica (using the SLAVEOF command), the
* initial MULTI was propagated into the replication backlog, but the
* rest was not. We need to make sure to at least terminate the
* backlog with the final EXEC. */
+186 -43
View File
@@ -99,6 +99,7 @@ client *createClient(int fd, int iel) {
* in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */
if (fd != -1) {
serverAssert(iel == (serverTL - g_pserver->rgthreadvar));
anetNonBlock(NULL,fd);
anetEnableTcpNoDelay(NULL,fd);
if (cserver.tcpkeepalive)
@@ -116,7 +117,7 @@ client *createClient(int fd, int iel) {
uint64_t client_id;
client_id = g_pserver->next_client_id.fetch_add(1);
c->iel = iel;
fastlock_init(&c->lock);
fastlock_init(&c->lock, "client");
c->id = client_id;
c->resp = 2;
c->fd = fd;
@@ -176,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);
@@ -195,7 +197,7 @@ client *createClient(int fd, int iel) {
* buffers can hold, then we'll really install the handler. */
void clientInstallWriteHandler(client *c) {
/* Schedule the client to write the output buffers to the socket only
* if not already done and, for slaves, if the slave can actually receive
* if not already done and, for slaves, if the replica can actually receive
* writes at this stage. */
if (!(c->flags & CLIENT_PENDING_WRITE) &&
(c->replstate == REPL_STATE_NONE ||
@@ -239,7 +241,7 @@ void clientInstallAsyncWriteHandler(client *c) {
*
* 1) The event handler should already be installed since the output buffer
* already contains something.
* 2) The client is a slave but not yet online, so we want to just accumulate
* 2) The client is a replica but not yet online, so we want to just accumulate
* writes in the buffer but not actually sending them yet.
*
* Typically gets called every time a reply is built, before adding more
@@ -247,8 +249,12 @@ void clientInstallAsyncWriteHandler(client *c) {
* data should be appended to the output buffers. */
int prepareClientToWrite(client *c, bool fAsync) {
fAsync = fAsync && !FCorrectThread(c); // Not async if we're on the right thread
serverAssert(!fAsync || GlobalLocksAcquired());
serverAssert(c->fd <= 0 || c->lock.fOwnLock());
serverAssert(FCorrectThread(c) || fAsync);
if (FCorrectThread(c)) {
serverAssert(c->fd <= 0 || c->lock.fOwnLock());
} else {
serverAssert(GlobalLocksAcquired());
}
if (c->flags & CLIENT_FORCE_REPLY) return C_OK; // FORCE REPLY means we're doing something else with the buffer.
// do not install a write handler
@@ -427,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:
*
@@ -442,7 +476,7 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
addReplyProtoCore(c,s,len,fAsync);
addReplyProtoCore(c,"\r\n",2,fAsync);
/* Sometimes it could be normal that a slave replies to a master with
/* Sometimes it could be normal that a replica replies to a master with
* an error and this function gets called. Actually the error will never
* be sent because addReply*() against master clients has no effect...
* A notable example is:
@@ -459,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;
}
}
@@ -998,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;
@@ -1100,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);
@@ -1117,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;
}
}
}
}
@@ -1146,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();
@@ -1214,7 +1301,7 @@ void unlinkClient(client *c) {
/* In the case of diskless replication the fork is writing to the
* sockets and just closing the fd isn't enough, if we don't also
* shutdown the socket the fork will continue to write to the slave
* shutdown the socket the fork will continue to write to the replica
* and the salve will only find out that it was disconnected when
* it will finish reading the rdb. */
if ((c->flags & CLIENT_SLAVE) &&
@@ -1299,7 +1386,7 @@ bool freeClient(client *c) {
}
}
/* Log link disconnection with slave */
/* Log link disconnection with replica */
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
serverLog(LL_WARNING,"Connection with replica %s lost.",
replicationGetSlaveName(c));
@@ -1333,8 +1420,8 @@ bool freeClient(client *c) {
* places where active clients may be referenced. */
unlinkClient(c);
/* Master/slave cleanup Case 1:
* we lost the connection with a slave. */
/* Master/replica cleanup Case 1:
* we lost the connection with a replica. */
if (c->flags & CLIENT_SLAVE) {
if (c->replstate == SLAVE_STATE_SEND_BULK) {
if (c->repldbfd != -1) close(c->repldbfd);
@@ -1352,7 +1439,7 @@ bool freeClient(client *c) {
refreshGoodSlavesCount();
}
/* Master/slave cleanup Case 2:
/* Master/replica cleanup Case 2:
* we lost the connection with the master. */
if (c->flags & CLIENT_MASTER) replicationHandleMasterDisconnection(MasterInfoFromClient(c));
@@ -1494,7 +1581,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
* just deliver as much data as it is possible to deliver.
*
* Moreover, we also send as much as possible if the client is
* a slave (otherwise, on high-speed traffic, the replication
* a replica (otherwise, on high-speed traffic, the replication
* buffer will grow indefinitely) */
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
(g_pserver->maxmemory == 0 ||
@@ -1509,7 +1596,6 @@ int writeToClient(int fd, client *c, int handler_installed) {
} else {
serverLog(LL_VERBOSE,
"Error writing to client: %s", strerror(errno));
lock.unlock();
freeClientAsync(c);
return C_ERR;
@@ -1528,7 +1614,6 @@ int writeToClient(int fd, client *c, int handler_installed) {
/* Close connection after entire reply has been sent. */
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
lock.unlock();
freeClientAsync(c);
return C_ERR;
}
@@ -1548,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();
}
}
}
@@ -1805,7 +1893,7 @@ int processInlineBuffer(client *c) {
}
/* Newline from slaves can be used to refresh the last ACK time.
* This is useful for a slave to ping back while loading a big
* This is useful for a replica to ping back while loading a big
* RDB file. */
if (querylen == 0 && c->flags & CLIENT_SLAVE)
c->repl_ack_time = g_pserver->unixtime;
@@ -2038,8 +2126,8 @@ int processCommandAndResetClient(client *c, int flags) {
}
if (serverTL->current_client == NULL) deadclient = 1;
serverTL->current_client = NULL;
/* freeMemoryIfNeeded may flush slave output buffers. This may
* result into a slave, that may be the active client, to be
/* freeMemoryIfNeeded may flush replica output buffers. This may
* result into a replica, that may be the active client, to be
* freed. */
return deadclient ? C_ERR : C_OK;
}
@@ -2060,7 +2148,7 @@ void processInputBuffer(client *c, int callFlags) {
if (c->flags & CLIENT_BLOCKED) break;
/* Don't process input from the master while there is a busy script
* condition on the slave. We want just to accumulate the replication
* condition on the replica. We want just to accumulate the replication
* stream (instead of replying -BUSY like we do with other clients) and
* later resume the processing. */
if (g_pserver->lua_timedout && c->flags & CLIENT_MASTER) break;
@@ -2519,9 +2607,17 @@ NULL
close_this_client = 1;
} else {
if (FCorrectThread(client))
{
freeClient(client);
}
else
{
int iel = client->iel;
freeClientAsync(client);
aePostFunction(g_pserver->rgthreadvar[client->iel].el, [iel] { // note: failure is OK
freeClientsInAsyncFreeQueue(iel);
});
}
}
killed++;
}
@@ -2910,10 +3006,10 @@ void flushSlavesOutputBuffers(void) {
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *slave = (client*)listNodeValue(ln);
client *replica = (client*)listNodeValue(ln);
int events;
if (!FCorrectThread(slave))
if (!FCorrectThread(replica))
continue; // we cannot synchronously flush other thread's clients
/* Note that the following will not flush output buffers of slaves
@@ -2922,12 +3018,12 @@ void flushSlavesOutputBuffers(void) {
* of put_online_on_ack is to postpone the moment it is installed.
* This is what we want since slaves in this state should not receive
* writes before the first ACK. */
events = aeGetFileEvents(g_pserver->rgthreadvar[slave->iel].el,slave->fd);
events = aeGetFileEvents(g_pserver->rgthreadvar[replica->iel].el,replica->fd);
if (events & AE_WRITABLE &&
slave->replstate == SLAVE_STATE_ONLINE &&
clientHasPendingReplies(slave))
replica->replstate == SLAVE_STATE_ONLINE &&
clientHasPendingReplies(replica))
{
writeToClient(slave->fd,slave,0);
writeToClient(replica->fd,replica,0);
}
}
}
@@ -2950,38 +3046,48 @@ void flushSlavesOutputBuffers(void) {
* than the time left for the previous pause, no change is made to the
* left duration. */
void pauseClients(mstime_t end) {
if (!g_pserver->clients_paused || end > g_pserver->clients_pause_end_time)
serverAssert(GlobalLocksAcquired());
if (!serverTL->clients_paused || end > g_pserver->clients_pause_end_time)
g_pserver->clients_pause_end_time = end;
g_pserver->clients_paused = 1;
for (int iel = 0; iel < cserver.cthreads; ++iel)
{
g_pserver->rgthreadvar[iel].clients_paused = true;
}
}
/* Return non-zero if clients are currently paused. As a side effect the
* function checks if the pause time was reached and clear it. */
int clientsArePaused(void) {
if (g_pserver->clients_paused &&
return serverTL->clients_paused;
}
void unpauseClientsIfNecessary()
{
serverAssert(GlobalLocksAcquired());
if (serverTL->clients_paused &&
g_pserver->clients_pause_end_time < g_pserver->mstime)
{
aeAcquireLock();
listNode *ln;
listIter li;
client *c;
g_pserver->clients_paused = 0;
serverTL->clients_paused = 0;
/* Put all the clients in the unblocked clients queue in order to
* force the re-processing of the input buffer if any. */
listRewind(g_pserver->clients,&li);
while ((ln = listNext(&li)) != NULL) {
c = (client*)listNodeValue(ln);
if (!FCorrectThread(c))
continue;
/* Don't touch slaves and blocked clients.
* The latter pending requests will be processed when unblocked. */
if (c->flags & (CLIENT_SLAVE|CLIENT_BLOCKED)) continue;
queueClientForReprocessing(c);
}
aeReleaseLock();
}
return g_pserver->clients_paused;
}
/* This function is called by Redis in order to process a few events from
@@ -2997,18 +3103,55 @@ int clientsArePaused(void) {
*
* 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;
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;
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)
{
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);
}
}
aeAcquireLock();
aeReleaseLock();
try
{
while (iterations--) {
int events = 0;
events += aeProcessEvents(g_pserver->rgthreadvar[iel].el, AE_FILE_EVENTS|AE_DONT_WAIT);
events += handleClientsWithPendingWrites(iel);
if (!events) break;
count += events;
}
}
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;
locker.arm(nullptr);
locker.release();
for (client *c : vecclients)
c->lock.lock();
return count;
}
+34
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());
@@ -714,6 +716,34 @@ int getLongLongFromObject(robj *o, long long *target) {
return C_OK;
}
int getUnsignedLongLongFromObject(robj *o, uint64_t *target) {
uint64_t value;
if (o == NULL) {
value = 0;
} else {
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
if (sdsEncodedObject(o)) {
char *pchEnd = nullptr;
errno = 0;
value = strtoull(szFromObj(o), &pchEnd, 10);
if (value == 0) {
// potential error
if (errno != 0)
return C_ERR;
if (pchEnd == szFromObj(o))
return C_ERR;
}
} else if (o->encoding == OBJ_ENCODING_INT) {
value = (long)ptrFromObj(o);
} else {
serverPanic("Unknown string encoding");
}
}
if (target) *target = value;
return C_OK;
}
int getLongLongFromObjectOrReply(client *c, robj *o, long long *target, const char *msg) {
long long value;
if (getLongLongFromObject(o, &value) != C_OK) {
@@ -1302,6 +1332,10 @@ NULL
* because we update the access time only
* when the key is read or overwritten. */
addReplyLongLong(c,LFUDecrAndReturn(o));
} else if (!strcasecmp(szFromObj(c->argv[1]), "lastmodified") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
== NULL) return;
addReplyLongLong(c, (g_pserver->mstime - (o->mvcc_tstamp >> MVCC_MS_SHIFT)) / 1000);
} else {
addReplySubcommandSyntaxError(c);
}
+8 -4
View File
@@ -301,9 +301,11 @@ int pubsubPublishMessage(robj *channel, robj *message) {
client *c = reinterpret_cast<client*>(ln->value);
if (c->flags & CLIENT_CLOSE_ASAP) // avoid blocking if the write will be ignored
continue;
fastlock_lock(&c->lock);
if (FCorrectThread(c))
fastlock_lock(&c->lock);
addReplyPubsubMessage(c,channel,message);
fastlock_unlock(&c->lock);
if (FCorrectThread(c))
fastlock_unlock(&c->lock);
receivers++;
}
}
@@ -321,10 +323,12 @@ int pubsubPublishMessage(robj *channel, robj *message) {
{
if (pat->pclient->flags & CLIENT_CLOSE_ASAP)
continue;
fastlock_lock(&pat->pclient->lock);
if (FCorrectThread(pat->pclient))
fastlock_lock(&pat->pclient->lock);
addReplyPubsubPatMessage(pat->pclient,
pat->pattern,channel,message);
fastlock_unlock(&pat->pclient->lock);
if (FCorrectThread(pat->pclient))
fastlock_unlock(&pat->pclient->lock);
receivers++;
}
}
+103 -40
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));
@@ -2107,19 +2144,31 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
/* Read key */
if (key != nullptr)
{
decrRefCount(key);
key = nullptr;
}
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
/* Read value */
if ((val = rdbLoadObject(type,rdb,key, mvcc_tstamp)) == NULL) goto eoferr;
bool fStaleMvccKey = val->mvcc_tstamp < rsi->mvccMinThreshold;
/* Check if the key already expired. This function is used when loading
* an RDB file from disk, either at startup, or when an RDB was
* received from the master. In the latter case, the master is
* responsible for key expiry. If we would expire keys here, the
* snapshot taken by the master may not be reflected on the slave. */
if (listLength(g_pserver->masters) == 0 && !loading_aof && expiretime != -1 && expiretime < now) {
* snapshot taken by the master may not be reflected on the replica. */
bool fExpiredKey = (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica) && !loading_aof && expiretime != -1 && expiretime < now;
if (fStaleMvccKey || fExpiredKey) {
if (fStaleMvccKey && !fExpiredKey && rsi->mi != nullptr && rsi->mi->staleKeyMap != nullptr && lookupKeyRead(db, key) == nullptr) {
// We have a key that we've already deleted and is not back in our database.
// We'll need to inform the sending master of the delete if it is also a replica of us
rsi->mi->staleKeyMap->operator[](db - g_pserver->db).push_back(key);
}
decrRefCount(key);
key = nullptr;
decrRefCount(val);
val = nullptr;
} else {
/* Add the new object in the hash table */
int fInserted = dbMerge(db, key, val, rsi->fForceSetKey); // Note: dbMerge will incrRef
@@ -2136,6 +2185,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
else
{
decrRefCount(val);
val = nullptr;
}
}
@@ -2147,7 +2197,10 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
}
if (key != nullptr)
{
decrRefCount(key);
key = nullptr;
}
if (subexpireKey != nullptr)
{
@@ -2174,12 +2227,22 @@ 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 */
}
int rdbLoadFile(char *filename, rdbSaveInfo *rsi);
int rdbLoad(rdbSaveInfo *rsi)
{
int err = C_ERR;
@@ -2199,7 +2262,7 @@ int rdbLoad(rdbSaveInfo *rsi)
*
* If you pass an 'rsi' structure initialied with RDB_SAVE_OPTION_INIT, the
* loading code will fiil the information fields in the structure. */
int rdbLoadFile(char *filename, rdbSaveInfo *rsi) {
int rdbLoadFile(const char *filename, rdbSaveInfo *rsi) {
FILE *fp;
rio rdb;
int retval;
@@ -2268,7 +2331,7 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
g_pserver->rdb_child_type = RDB_CHILD_TYPE_NONE;
g_pserver->rdb_save_time_start = -1;
/* If the child returns an OK exit code, read the set of slave client
/* If the child returns an OK exit code, read the set of replica client
* IDs and the associated status code. We'll terminate all the slaves
* in error state.
*
@@ -2307,35 +2370,35 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *slave = (client*)ln->value;
client *replica = (client*)ln->value;
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
uint64_t j;
int errorcode = 0;
/* Search for the slave ID in the reply. In order for a slave to
/* Search for the replica ID in the reply. In order for a replica to
* continue the replication process, we need to find it in the list,
* and it must have an error code set to 0 (which means success). */
for (j = 0; j < ok_slaves[0]; j++) {
if (slave->id == ok_slaves[2*j+1]) {
if (replica->id == ok_slaves[2*j+1]) {
errorcode = ok_slaves[2*j+2];
break; /* Found in slaves list. */
}
}
if (j == ok_slaves[0] || errorcode != 0) {
serverLog(LL_WARNING,
"Closing slave %s: child->slave RDB transfer failed: %s",
replicationGetSlaveName(slave),
"Closing replica %s: child->replica RDB transfer failed: %s",
replicationGetSlaveName(replica),
(errorcode == 0) ? "RDB transfer child aborted"
: strerror(errorcode));
freeClient(slave);
freeClient(replica);
} else {
serverLog(LL_WARNING,
"Slave %s correctly received the streamed RDB file.",
replicationGetSlaveName(slave));
replicationGetSlaveName(replica));
/* Restore the socket as non-blocking. */
anetNonBlock(NULL,slave->fd);
anetSendTimeout(NULL,slave->fd,0);
anetNonBlock(NULL,replica->fd);
anetSendTimeout(NULL,replica->fd,0);
}
}
}
@@ -2402,17 +2465,17 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *slave = (client*)ln->value;
client *replica = (client*)ln->value;
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
clientids[numfds] = slave->id;
fds[numfds++] = slave->fd;
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
clientids[numfds] = replica->id;
fds[numfds++] = replica->fd;
replicationSetupSlaveForFullResync(replica,getPsyncInitialOffset());
/* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the children returns (since duped socket
* will share the O_NONBLOCK attribute with the parent). */
anetBlock(NULL,slave->fd);
anetSendTimeout(NULL,slave->fd,g_pserver->repl_timeout*1000);
anetBlock(NULL,replica->fd);
anetSendTimeout(NULL,replica->fd,g_pserver->repl_timeout*1000);
}
}
@@ -2446,19 +2509,19 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
g_pserver->child_info_data.cow_size = private_dirty;
sendChildInfo(CHILD_INFO_TYPE_RDB);
/* If we are returning OK, at least one slave was served
/* If we are returning OK, at least one replica was served
* with the RDB file as expected, so we need to send a report
* to the parent via the pipe. The format of the message is:
*
* <len> <slave[0].id> <slave[0].error> ...
* <len> <replica[0].id> <replica[0].error> ...
*
* len, slave IDs, and slave errors, are all uint64_t integers,
* len, replica IDs, and replica errors, are all uint64_t integers,
* so basically the reply is composed of 64 bits for the len field
* plus 2 additional 64 bit integers for each entry, for a total
* of 'len' entries.
*
* The 'id' represents the slave's client ID, so that the master
* can match the report with a specific slave, and 'error' is
* The 'id' represents the replica's client ID, so that the master
* can match the report with a specific replica, and 'error' is
* set to 0 if the replication process terminated with a success
* or the error code if an error occurred. */
void *msg = zmalloc(sizeof(uint64_t)*(1+2*numfds), MALLOC_LOCAL);
@@ -2499,12 +2562,12 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *slave = (client*)ln->value;
client *replica = (client*)ln->value;
int j;
for (j = 0; j < numfds; j++) {
if (slave->id == clientids[j]) {
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
if (replica->id == clientids[j]) {
replica->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
break;
}
}
@@ -2598,17 +2661,17 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
/* If the instance is a master, we can populate the replication info
* only when repl_backlog is not NULL. If the repl_backlog is NULL,
* it means that the instance isn't in any replication chains. In this
* scenario the replication info is useless, because when a slave
* scenario the replication info is useless, because when a replica
* connects to us, the NULL repl_backlog will trigger a full
* synchronization, at the same time we will use a new replid and clear
* replid2. */
if (g_pserver->fActiveReplica || (!listLength(g_pserver->masters) && g_pserver->repl_backlog)) {
/* Note that when g_pserver->slaveseldb is -1, it means that this master
/* Note that when g_pserver->replicaseldb is -1, it means that this master
* didn't apply any write commands after a full synchronization.
* So we can let repl_stream_db be 0, this allows a restarted slave
* So we can let repl_stream_db be 0, this allows a restarted replica
* to reload replication ID/offset, it's safe because the next write
* command must generate a SELECT statement. */
rsi->repl_stream_db = g_pserver->slaveseldb == -1 ? 0 : g_pserver->slaveseldb;
rsi->repl_stream_db = g_pserver->replicaseldb == -1 ? 0 : g_pserver->replicaseldb;
return rsi;
}
@@ -2619,7 +2682,7 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
}
struct redisMaster *miFirst = (redisMaster*)(listLength(g_pserver->masters) ? listNodeValue(listFirst(g_pserver->masters)) : NULL);
/* If the instance is a slave we need a connected master
/* If the instance is a replica we need a connected master
* in order to fetch the currently selected DB. */
if (miFirst && miFirst->master) {
rsi->repl_stream_db = miFirst->master->db->id;
@@ -2627,7 +2690,7 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
}
/* If we have a cached master we can use it in order to populate the
* replication selected DB info inside the RDB file: the slave can
* replication selected DB info inside the RDB file: the replica can
* increment the master_repl_offset only from data arriving from the
* master, so if we are disconnected the offset in the cached master
* is valid. */
+5 -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. */
@@ -136,6 +139,7 @@ int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
int rdbSaveObjectType(rio *rdb, robj_roptr o);
int rdbLoadObjectType(rio *rdb);
int rdbLoad(rdbSaveInfo *rsi);
int rdbLoadFile(const char *filename, rdbSaveInfo *rsi);
int rdbSaveBackground(rdbSaveInfo *rsi);
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
void rdbRemoveTempFile(pid_t childpid);
+3 -1
View File
@@ -152,7 +152,7 @@ typedef struct clusterNode {
int port;
sds name;
int flags;
sds replicate; /* Master ID if node is a slave */
sds replicate; /* Master ID if node is a replica */
int *slots;
int slots_count;
int current_slot_index;
@@ -173,6 +173,8 @@ typedef struct redisConfig {
sds appendonly;
} redisConfig;
int g_fInCrash = false;
/* Prototypes */
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
static void createMissingClients(client c);
+2 -2
View File
@@ -124,8 +124,8 @@ extern "C" void freeClusterManager(void) {
*
* The score is calculated as follows:
*
* SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
* SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
* SAME_AS_MASTER = 10000 * each replica in the same IP of its master.
* SAME_AS_SLAVE = 1 * each replica having the same IP as another replica
of the same master.
* FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
*
+7 -111
View File
@@ -57,6 +57,7 @@
#include "anet.h"
#include "ae.h"
#include "storage.h"
#include "motd.h"
#include "redis-cli.h"
@@ -90,6 +91,11 @@ int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253
int *spectrum_palette;
int spectrum_palette_size;
int g_fInCrash = 0;
const char *motd_url = "http://api.keydb.dev/motd/motd_cli.txt";
const char *motd_cache_file = "/.keydb-cli-motd";
/*------------------------------------------------------------------------------
* Utility functions
*--------------------------------------------------------------------------- */
@@ -6534,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()
*--------------------------------------------------------------------------- */
@@ -6814,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);
+1 -1
View File
@@ -187,7 +187,7 @@ typedef struct clusterManagerNode {
time_t ping_recv;
int flags;
list *flags_str; /* Flags string representations */
sds replicate; /* Master ID if node is a slave */
sds replicate; /* Master ID if node is a replica */
int dirty; /* Node has changes that can be flushed */
uint8_t slots[CLUSTER_MANAGER_SLOTS];
int slots_count;
+2 -2
View File
@@ -71,9 +71,9 @@ extern "C" {
#define REDISMODULE_CTX_FLAGS_MULTI (1<<1)
/* The instance is a master */
#define REDISMODULE_CTX_FLAGS_MASTER (1<<2)
/* The instance is a slave */
/* The instance is a replica */
#define REDISMODULE_CTX_FLAGS_SLAVE (1<<3)
/* The instance is read-only (usually meaning it's a slave as well) */
/* The instance is read-only (usually meaning it's a replica as well) */
#define REDISMODULE_CTX_FLAGS_READONLY (1<<4)
/* The instance is running in cluster mode */
#define REDISMODULE_CTX_FLAGS_CLUSTER (1<<5)
+500 -250
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1479,7 +1479,7 @@ void evalGenericCommand(client *c, int evalsha) {
* To do so we use a cache of SHA1s of scripts that we already propagated
* as full EVAL, that's called the Replication Script Cache.
*
* For repliation, everytime a new slave attaches to the master, we need to
* For repliation, everytime a new replica attaches to the master, we need to
* flush our cache of scripts that can be replicated as EVALSHA, while
* for AOF we need to do so every time we rewrite the AOF file. */
if (evalsha && !g_pserver->lua_replicate_commands) {
+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
+171 -90
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>
@@ -59,9 +60,13 @@
#include <sys/socket.h>
#include <algorithm>
#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 */
@@ -153,7 +158,7 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
*
* ok-loading: Allow the command while loading the database.
*
* ok-stale: Allow the command while a slave has stale data but is not
* ok-stale: Allow the command while a replica has stale data but is not
* allowed to serve this data. Normally no command is accepted
* in this condition but just a few.
*
@@ -228,6 +233,10 @@ struct redisCommand redisCommandTable[] = {
"write @keyspace",
0,NULL,1,-1,1,0,0,0},
{"expdel",delCommand,-2,
"write @keyspace",
0,NULL,1,-1,1,0,0,0},
{"unlink",unlinkCommand,-2,
"write fast @keyspace",
0,NULL,1,-1,1,0,0,0},
@@ -618,7 +627,11 @@ struct redisCommand redisCommandTable[] = {
"write fast @keyspace",
0,NULL,1,1,1,0,0,0},
{"expiremember", expireMemberCommand, 4,
{"expiremember", expireMemberCommand, -4,
"write fast @keyspace",
0,NULL,1,1,1,0,0,0},
{"expirememberat", expireMemberAtCommand, 4,
"write fast @keyspace",
0,NULL,1,1,1,0,0,0},
@@ -725,7 +738,7 @@ struct redisCommand redisCommandTable[] = {
"admin no-script",
0,NULL,0,0,0,0,0,0},
{"ttl",ttlCommand,2,
{"ttl",ttlCommand,-2,
"read-only fast random @keyspace",
0,NULL,1,1,1,0,0,0},
@@ -733,11 +746,11 @@ struct redisCommand redisCommandTable[] = {
"read-only fast @keyspace",
0,NULL,1,-1,1,0,0,0},
{"pttl",pttlCommand,2,
{"pttl",pttlCommand,-2,
"read-only fast random @keyspace",
0,NULL,1,1,1,0,0,0},
{"persist",persistCommand,2,
{"persist",persistCommand,-2,
"write fast @keyspace",
0,NULL,1,1,1,0,0,0},
@@ -867,10 +880,6 @@ struct redisCommand redisCommandTable[] = {
"write use-memory @bitmap",
0,NULL,2,-1,1,0,0,0},
{"bitoprange",bitopRangeCommand,-5,
"write use-memory @bitmap",
0,NULL,2,-1,1,0,0,0},
{"bitcount",bitcountCommand,-2,
"read-only @bitmap",
0,NULL,1,1,1,0,0,0},
@@ -1018,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 ============================ */
@@ -1026,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. */
@@ -1063,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);
@@ -1680,13 +1694,16 @@ 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:
fastlock_unlock(&c->lock);
}
}
/* Free any pending clients */
freeClientsInAsyncFreeQueue(iel);
}
/* This function handles 'background' operations we are required to do
@@ -1695,9 +1712,9 @@ void clientsCron(int iel) {
void databasesCron(void) {
/* Expire keys by random sampling. Not required for slaves
* as master will synthesize DELs for us. */
if (g_pserver->active_expire_enabled && listLength(g_pserver->masters) == 0) {
if (g_pserver->active_expire_enabled && (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica)) {
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
} else if (listLength(g_pserver->masters)) {
} else if (listLength(g_pserver->masters) && !g_pserver->fActiveReplica) {
expireSlaveKeys();
}
@@ -1807,6 +1824,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Update the time cache. */
updateCachedTime();
/* Unpause clients if enough time has elapsed */
unpauseClientsIfNecessary();
g_pserver->hz = g_pserver->config_hz;
/* Adapt the g_pserver->hz value to the number of configured clients. If we have
* many clients, we want to call serverCron() with an higher frequency. */
@@ -1814,7 +1834,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
while (listLength(g_pserver->clients) / g_pserver->hz >
MAX_CLIENTS_PER_CLOCK_TICK)
{
g_pserver->hz *= 2;
g_pserver->hz += g_pserver->hz; // *= 2
if (g_pserver->hz > CONFIG_MAX_HZ) {
g_pserver->hz = CONFIG_MAX_HZ;
break;
@@ -1878,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;
}
@@ -2014,9 +2034,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
flushAppendOnlyFile(0);
}
/* Clear the paused clients flag if needed. */
clientsArePaused(); /* Don't check return value, just use the side effect.*/
/* Replication cron function -- used to reconnect to master,
* detect transfer failures, start background RDB transfers and so forth. */
run_with_period(1000) replicationCron();
@@ -2073,6 +2090,9 @@ int serverCronLite(struct aeEventLoop *eventLoop, long long id, void *clientData
{
processUnblockedClients(iel);
}
/* Unpause clients if enough time has elapsed */
unpauseClientsIfNecessary();
ProcessPendingAsyncWrites(); // A bug but leave for now, events should clean up after themselves
clientsCron(iel);
@@ -2094,7 +2114,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
/* Run a fast expire cycle (the called function will return
* ASAP if a fast cycle is not needed). */
if (g_pserver->active_expire_enabled && listLength(g_pserver->masters) == 0)
if (g_pserver->active_expire_enabled && (listLength(g_pserver->masters) == 0 || g_pserver->fActiveReplica))
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);
/* Send all the slaves an ACK request if at least one client blocked
@@ -2105,7 +2125,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
argv[0] = createStringObject("REPLCONF",8);
argv[1] = createStringObject("GETACK",6);
argv[2] = createStringObject("*",1); /* Not used argument. */
replicationFeedSlaves(g_pserver->slaves, g_pserver->slaveseldb, argv, 3);
replicationFeedSlaves(g_pserver->slaves, g_pserver->replicaseldb, argv, 3);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
decrRefCount(argv[2]);
@@ -2308,6 +2328,7 @@ void initMasterInfo(redisMaster *master)
master->repl_state = REPL_STATE_NONE;
master->repl_down_since = 0; /* Never connected, repl is down since EVER. */
master->mvccLastSync = 0;
}
void initServerConfig(void) {
@@ -2475,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();
@@ -2510,8 +2531,8 @@ void initServerConfig(void) {
/* By default we want scripts to be always replicated by effects
* (single commands executed by the script), and not by sending the
* script to the slave / AOF. This is the new way starting from
* Redis 5. However it is possible to revert it via redis.conf. */
* script to the replica / AOF. This is the new way starting from
* Redis 5. However it is possible to revert it via keydb.conf. */
g_pserver->lua_always_replicate_commands = 1;
/* Multithreading */
@@ -2703,7 +2724,7 @@ void checkTcpBacklogSettings(void) {
* impossible to bind, or no bind addresses were specified in the server
* configuration but the function is not able to bind * for at least
* one of the IPv4 or IPv6 protocols. */
int listenToPort(int port, int *fds, int *count, int fReusePort) {
int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListen) {
int j;
/* Force binding of 0.0.0.0 if no bind address is specified, always
@@ -2715,7 +2736,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
/* Bind * for both IPv6 and IPv4, we enter here only if
* g_pserver->bindaddr_count == 0. */
fds[*count] = anetTcp6Server(serverTL->neterr,port,NULL,
g_pserver->tcp_backlog, fReusePort);
g_pserver->tcp_backlog, fReusePort, fFirstListen);
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
(*count)++;
@@ -2727,7 +2748,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
if (*count == 1 || unsupported) {
/* Bind the IPv4 address as well. */
fds[*count] = anetTcpServer(serverTL->neterr,port,NULL,
g_pserver->tcp_backlog, fReusePort);
g_pserver->tcp_backlog, fReusePort, fFirstListen);
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
(*count)++;
@@ -2743,11 +2764,11 @@ int listenToPort(int port, int *fds, int *count, int fReusePort) {
} else if (strchr(g_pserver->bindaddr[j],':')) {
/* Bind IPv6 address. */
fds[*count] = anetTcp6Server(serverTL->neterr,port,g_pserver->bindaddr[j],
g_pserver->tcp_backlog, fReusePort);
g_pserver->tcp_backlog, fReusePort, fFirstListen);
} else {
/* Bind IPv4 address. */
fds[*count] = anetTcpServer(serverTL->neterr,port,g_pserver->bindaddr[j],
g_pserver->tcp_backlog, fReusePort);
g_pserver->tcp_backlog, fReusePort, fFirstListen);
}
if (fds[*count] == ANET_ERR) {
serverLog(LL_WARNING,
@@ -2809,7 +2830,7 @@ static void initNetworkingThread(int iel, int fReusePort)
if (fReusePort || (iel == IDX_EVENT_LOOP_MAIN))
{
if (g_pserver->port != 0 &&
listenToPort(g_pserver->port,g_pserver->rgthreadvar[iel].ipfd,&g_pserver->rgthreadvar[iel].ipfd_count, fReusePort) == C_ERR)
listenToPort(g_pserver->port,g_pserver->rgthreadvar[iel].ipfd,&g_pserver->rgthreadvar[iel].ipfd_count, fReusePort, (iel == IDX_EVENT_LOOP_MAIN)) == C_ERR)
exit(1);
}
else
@@ -2866,6 +2887,8 @@ static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
pvar->cclients = 0;
pvar->el = aeCreateEventLoop(g_pserver->maxclients+CONFIG_FDSET_INCR);
pvar->current_client = nullptr;
pvar->clients_paused = 0;
pvar->fRetrySetAofEvent = false;
if (pvar->el == NULL) {
serverLog(LL_WARNING,
"Failed creating the event loop. Error message: '%s'",
@@ -2873,7 +2896,7 @@ static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
exit(1);
}
fastlock_init(&pvar->lockPendingWrite);
fastlock_init(&pvar->lockPendingWrite, "lockPendingWrite");
if (!fMain)
{
@@ -2920,12 +2943,11 @@ void initServer(void) {
signal(SIGPIPE, SIG_IGN);
setupSignalHandlers();
fastlock_init(&g_pserver->flock);
g_pserver->db = (redisDb*)zmalloc(sizeof(redisDb)*cserver.dbnum, MALLOC_LOCAL);
/* Create the Redis databases, and initialize other internal state. */
for (int j = 0; j < cserver.dbnum; j++) {
new (&g_pserver->db[j]) redisDb;
g_pserver->db[j].pdict = dictCreate(&dbDictType,NULL);
g_pserver->db[j].setexpire = new(MALLOC_LOCAL) expireset();
g_pserver->db[j].expireitr = g_pserver->db[j].setexpire->end();
@@ -2959,11 +2981,10 @@ void initServer(void) {
cserver.pid = getpid();
g_pserver->clients_index = raxNew();
g_pserver->clients_to_close = listCreate();
g_pserver->slaveseldb = -1; /* Force to emit the first SELECT command. */
g_pserver->replicaseldb = -1; /* Force to emit the first SELECT command. */
g_pserver->ready_keys = listCreate();
g_pserver->clients_waiting_acks = listCreate();
g_pserver->get_ack_from_slaves = 0;
g_pserver->clients_paused = 0;
cserver.system_memory_size = zmalloc_get_memory_size();
createSharedObjects();
@@ -3128,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);
}
@@ -3203,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
@@ -3350,16 +3371,7 @@ void call(client *c, int flags) {
/* Call the command. */
dirty = g_pserver->dirty;
start = ustime();
try
{
c->cmd->proc(c);
}
catch (robj *obj)
{
addReply(c, obj);
decrRefCount(obj);
}
c->cmd->proc(c);
serverTL->commandsExecuted++;
duration = ustime()-start;
dirty = g_pserver->dirty-dirty;
@@ -3581,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
@@ -3595,8 +3607,8 @@ int processCommand(client *c, int callFlags) {
* propagation of DELs due to eviction. */
if (g_pserver->maxmemory && !g_pserver->lua_timedout) {
int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
/* freeMemoryIfNeeded may flush slave output buffers. This may result
* into a slave, that may be the active client, to be freed. */
/* freeMemoryIfNeeded may flush replica output buffers. This may result
* into a replica, that may be the active client, to be freed. */
if (serverTL->current_client == NULL) return C_ERR;
/* It was impossible to free enough memory, and the command the client
@@ -3643,7 +3655,7 @@ int processCommand(client *c, int callFlags) {
return C_OK;
}
/* Don't accept write commands if this is a read only slave. But
/* Don't accept write commands if this is a read only replica. But
* accept write commands if this is our master. */
if (listLength(g_pserver->masters) && g_pserver->repl_slave_ro &&
!(c->flags & CLIENT_MASTER) &&
@@ -3666,7 +3678,7 @@ int processCommand(client *c, int callFlags) {
}
/* Only allow commands with flag "t", such as INFO, SLAVEOF and so on,
* when slave-serve-stale-data is no and we are a slave with a broken
* when replica-serve-stale-data is no and we are a replica with a broken
* link with master. */
if (FBrokenLinkToMaster() &&
g_pserver->repl_serve_stale_data == 0 &&
@@ -3798,14 +3810,23 @@ int prepareForShutdown(int flags) {
unlink(cserver.pidfile);
}
/* Best effort flush of slave output buffers, so that we hopefully
/* Best effort flush of replica output buffers, so that we hopefully
* send them pending writes. */
flushSlavesOutputBuffers();
/* 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;
}
@@ -4093,7 +4114,7 @@ sds genRedisInfoString(const char *section) {
g_pserver->port,
(intmax_t)uptime,
(intmax_t)(uptime/(3600*24)),
g_pserver->hz,
g_pserver->hz.load(),
g_pserver->config_hz,
(unsigned long) lruclock,
cserver.executable ? cserver.executable : "",
@@ -4110,10 +4131,12 @@ sds genRedisInfoString(const char *section) {
"connected_clients:%lu\r\n"
"client_recent_max_input_buffer:%zu\r\n"
"client_recent_max_output_buffer:%zu\r\n"
"blocked_clients:%d\r\n",
"blocked_clients:%d\r\n"
"current_client_thread:%d\r\n",
listLength(g_pserver->clients)-listLength(g_pserver->slaves),
maxin, maxout,
g_pserver->blocked_clients);
g_pserver->blocked_clients,
static_cast<int>(serverTL - g_pserver->rgthreadvar));
for (int ithread = 0; ithread < cserver.cthreads; ++ithread)
{
info = sdscatprintf(info,
@@ -4474,18 +4497,18 @@ sds genRedisInfoString(const char *section) {
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *slave = (client*)listNodeValue(ln);
client *replica = (client*)listNodeValue(ln);
const char *state = NULL;
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
char ip[NET_IP_STR_LEN], *slaveip = replica->slave_ip;
int port;
long lag = 0;
if (slaveip[0] == '\0') {
if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1)
if (anetPeerToString(replica->fd,ip,sizeof(ip),&port) == -1)
continue;
slaveip = ip;
}
switch(slave->replstate) {
switch(replica->replstate) {
case SLAVE_STATE_WAIT_BGSAVE_START:
case SLAVE_STATE_WAIT_BGSAVE_END:
state = "wait_bgsave";
@@ -4498,14 +4521,14 @@ sds genRedisInfoString(const char *section) {
break;
}
if (state == NULL) continue;
if (slave->replstate == SLAVE_STATE_ONLINE)
lag = time(NULL) - slave->repl_ack_time;
if (replica->replstate == SLAVE_STATE_ONLINE)
lag = time(NULL) - replica->repl_ack_time;
info = sdscatprintf(info,
"slave%d:ip=%s,port=%d,state=%s,"
"offset=%lld,lag=%ld\r\n",
slaveid,slaveip,slave->slave_listening_port,state,
(slave->repl_ack_off + slave->reploff_skipped), lag);
slaveid,slaveip,replica->slave_listening_port,state,
(replica->repl_ack_off + replica->reploff_skipped), lag);
slaveid++;
}
}
@@ -4613,7 +4636,7 @@ void infoCommand(client *c) {
}
void monitorCommand(client *c) {
/* ignore MONITOR if already slave or in monitor mode */
/* ignore MONITOR if already replica or in monitor mode */
serverAssert(GlobalLocksAcquired());
if (c->flags & CLIENT_SLAVE) return;
@@ -4680,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,
@@ -4691,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");
@@ -4701,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);
@@ -4718,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))) ||
@@ -4730,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);
@@ -4840,7 +4867,7 @@ void loadDataFromDisk(void) {
while ((ln = listNext(&li)))
{
redisMaster *mi = (redisMaster*)listNodeValue(ln);
/* If we are a slave, create a cached master from this
/* If we are a replica, create a cached master from this
* information, in order to allow partial resynchronizations
* with masters. */
replicationCacheMasterUsingMyself(mi);
@@ -4860,6 +4887,12 @@ void redisOutOfMemoryHandler(size_t allocation_size) {
serverPanic("Redis aborting for OUT OF MEMORY");
}
void fuzzOutOfMemoryHandler(size_t allocation_size) {
serverLog(LL_WARNING,"Out Of Memory allocating %zu bytes!",
allocation_size);
exit(EXIT_FAILURE); // don't crash because it causes false positives
}
void redisSetProcTitle(const char *title) {
#ifdef USE_SETPROCTITLE
const char *server_mode = "";
@@ -4982,17 +5015,18 @@ void incrementMvccTstamp()
{
uint64_t msPrev;
__atomic_load(&g_pserver->mvcc_tstamp, &msPrev, __ATOMIC_ACQUIRE);
msPrev >>= 20; // convert to milliseconds
msPrev >>= MVCC_MS_SHIFT; // convert to milliseconds
long long mst;
__atomic_load(&g_pserver->mstime, &mst, __ATOMIC_RELAXED);
__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) << 20);
uint64_t val = ((uint64_t)g_pserver->mstime) << MVCC_MS_SHIFT;
__atomic_store(&g_pserver->mvcc_tstamp, &val, __ATOMIC_RELEASE);
}
}
@@ -5003,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)
@@ -5017,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;
@@ -5073,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
@@ -5173,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",
@@ -5188,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 */);
@@ -5214,6 +5276,23 @@ int main(int argc, char **argv) {
#endif
moduleLoadFromQueue();
ACLLoadUsersAtStartup();
// special case of FUZZING load from stdin then quit
if (argc > 1 && strstr(argv[1],"rdbfuzz-mode") != NULL)
{
zmalloc_set_oom_handler(fuzzOutOfMemoryHandler);
#ifdef __AFL_HAVE_MANUAL_CONTROL
__AFL_INIT();
#endif
rio rdb;
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
startLoading(stdin);
rioInitWithFile(&rdb,stdin);
rdbLoadRio(&rdb,&rsi,0);
stopLoading();
return EXIT_SUCCESS;
}
loadDataFromDisk();
if (g_pserver->cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
@@ -5250,7 +5329,7 @@ int main(int argc, char **argv) {
aeReleaseLock(); //Finally we can dump the lock
moduleReleaseGIL(true);
serverAssert(cserver.cthreads > 0 && cserver.cthreads <= MAX_EVENT_LOOPS);
pthread_t rgthread[MAX_EVENT_LOOPS];
for (int iel = 0; iel < cserver.cthreads; ++iel)
@@ -5281,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;
}
+156 -60
View File
@@ -54,6 +54,8 @@
#include <vector>
#include <algorithm>
#include <memory>
#include <map>
#include <string>
#ifdef __cplusplus
extern "C" {
#include <lua.h>
@@ -95,8 +97,7 @@ extern int g_fTestMode;
struct redisObject;
class robj_roptr
{
protected:
const redisObject *m_ptr = nullptr;
const redisObject *m_ptr;
public:
robj_roptr()
@@ -145,43 +146,85 @@ public:
}
};
void incrRefCount(robj_roptr o);
void decrRefCount(robj_roptr o);
class robj_rorefptr : public robj_roptr
void incrRefCount(robj_roptr o);
class robj_sharedptr
{
redisObject *m_ptr;
public:
robj_rorefptr() = default;
robj_rorefptr(const redisObject *ref)
: robj_roptr(ref)
robj_sharedptr()
: m_ptr(nullptr)
{}
robj_rorefptr(const robj_rorefptr& other)
: robj_roptr(other)
{
if (m_ptr)
incrRefCount(m_ptr);
}
robj_rorefptr(robj_rorefptr&& other)
: robj_roptr(other)
{}
~robj_rorefptr()
robj_sharedptr(redisObject *ptr)
: m_ptr(ptr)
{
incrRefCount(ptr);
}
~robj_sharedptr()
{
if (m_ptr)
decrRefCount(m_ptr);
}
robj_rorefptr &operator=(robj_rorefptr &other)
robj_sharedptr(const robj_sharedptr& other)
{
m_ptr = other.m_ptr;
incrRefCount(m_ptr);
}
robj_sharedptr(robj_sharedptr&& other)
{
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
robj_sharedptr &operator=(const robj_sharedptr& other)
{
robj_roptr::operator=(other.m_ptr);
if (m_ptr)
incrRefCount(other);
decrRefCount(m_ptr);
m_ptr = other.m_ptr;
incrRefCount(m_ptr);
return *this;
}
using robj_roptr::operator=;
using robj_roptr::operator==;
using robj_roptr::operator->;
robj_sharedptr &operator=(redisObject *ptr)
{
if (m_ptr)
decrRefCount(m_ptr);
m_ptr = ptr;
incrRefCount(m_ptr);
return *this;
}
bool operator==(const robj_sharedptr &other) const
{
return m_ptr == other.m_ptr;
}
bool operator!=(const robj_sharedptr &other) const
{
return m_ptr != other.m_ptr;
}
redisObject* operator->() const
{
return m_ptr;
}
bool operator!() const
{
return !m_ptr;
}
operator bool() const{
return !!m_ptr;
}
operator redisObject *()
{
return (redisObject*)m_ptr;
}
redisObject *get() { return m_ptr; }
};
/* Error codes */
@@ -222,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. */
@@ -371,7 +414,7 @@ public:
/* Client flags */
#define CLIENT_SLAVE (1<<0) /* This client is a repliaca */
#define CLIENT_MASTER (1<<1) /* This client is a master */
#define CLIENT_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */
#define CLIENT_MONITOR (1<<2) /* This client is a replica monitor, see MONITOR */
#define CLIENT_MULTI (1<<3) /* This client is in a MULTI context */
#define CLIENT_BLOCKED (1<<4) /* The client is waiting in a blocking operation */
#define CLIENT_DIRTY_CAS (1<<5) /* Watched keys modified. EXEC will fail. */
@@ -435,7 +478,7 @@ public:
#define CLIENT_TYPE_MASTER 3 /* Master. */
#define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output
buffer configuration. Just the first
three: normal, slave, pubsub. */
three: normal, replica, pubsub. */
/* Slave replication state. Used in g_pserver->repl_state for slaves to remember
* what to do next. */
@@ -461,20 +504,21 @@ public:
#define REPL_STATE_CONNECTED 17 /* Connected to master */
/* State of slaves from the POV of the master. Used in client->replstate.
* In SEND_BULK and ONLINE state the slave receives new updates
* In SEND_BULK and ONLINE state the replica receives new updates
* in its output queue. In the WAIT_BGSAVE states instead the server is waiting
* to start the next background saving in order to send updates to it. */
#define SLAVE_STATE_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
#define SLAVE_STATE_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
#define SLAVE_STATE_SEND_BULK 8 /* Sending RDB file to slave. */
#define SLAVE_STATE_SEND_BULK 8 /* Sending RDB file to replica. */
#define SLAVE_STATE_ONLINE 9 /* RDB file transmitted, sending just updates. */
/* Slave capabilities. */
#define SLAVE_CAPA_NONE 0
#define SLAVE_CAPA_EOF (1<<0) /* Can parse the RDB EOF streaming format. */
#define SLAVE_CAPA_PSYNC2 (1<<1) /* Supports PSYNC2 protocol. */
#define SLAVE_CAPA_ACTIVE_EXPIRE (1<<2) /* Will the slave perform its own expirations? (Don't send delete) */
/* Synchronous read timeout - slave side */
/* Synchronous read timeout - replica side */
#define CONFIG_REPL_SYNCIO_TIMEOUT 5
/* List related stuff */
@@ -583,7 +627,7 @@ public:
/* RDB active child save type. */
#define RDB_CHILD_TYPE_NONE 0
#define RDB_CHILD_TYPE_DISK 1 /* RDB is written to disk. */
#define RDB_CHILD_TYPE_SOCKET 2 /* RDB is written to slave socket. */
#define RDB_CHILD_TYPE_SOCKET 2 /* RDB is written to replica socket. */
/* Keyspace changes notification classes. Every class is associated with a
* character for configuration purposes. */
@@ -640,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
@@ -762,6 +808,8 @@ typedef struct RedisModuleDigest {
#define OBJ_SHARED_REFCOUNT (0x7FFFFFFF)
#define OBJ_MVCC_INVALID (0xFFFFFFFFFFFFFFFFULL)
#define MVCC_MS_SHIFT 20
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
@@ -769,7 +817,7 @@ typedef struct redisObject {
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
private:
mutable std::atomic<unsigned> refcount;
mutable std::atomic<unsigned> refcount {0};
public:
uint64_t mvcc_tstamp;
void *m_ptr;
@@ -840,6 +888,24 @@ public:
void expireSubKey(const char *szSubkey, long long when)
{
// First check if the subkey already has an expiration
for (auto &entry : m_vecexpireEntries)
{
if (szSubkey != nullptr)
{
// if this is a subkey expiry then its not a match if the expireEntry is either for the
// primary key or a different subkey
if (entry.spsubkey == nullptr || sdscmp((sds)entry.spsubkey.get(), (sds)szSubkey) != 0)
continue;
}
else
{
if (entry.spsubkey != nullptr)
continue;
}
m_vecexpireEntries.erase(m_vecexpireEntries.begin() + (&entry - m_vecexpireEntries.data()));
break;
}
auto itrInsert = std::lower_bound(m_vecexpireEntries.begin(), m_vecexpireEntries.end(), when);
const char *subkey = (szSubkey) ? sdsdup(szSubkey) : nullptr;
m_vecexpireEntries.emplace(itrInsert, when, subkey);
@@ -863,6 +929,7 @@ class expireEntry {
public:
class iter
{
friend class expireEntry;
expireEntry *m_pentry = nullptr;
size_t m_idx = 0;
@@ -998,6 +1065,14 @@ public:
return iter(this, u.m_pfatentry->size());
return iter(this, 1);
}
void erase(iter &itr)
{
if (!FFat())
throw -1; // assert
pfatentry()->m_vecexpireEntries.erase(
pfatentry()->m_vecexpireEntries.begin() + itr.m_idx);
}
bool FGetPrimaryExpire(long long *pwhen)
{
@@ -1053,7 +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;
@@ -1065,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 {
@@ -1208,8 +1289,8 @@ typedef struct client {
int casyncOpsPending;
int fPendingAsyncWrite; /* NOTE: Not a flag because it is written to outside of the client lock (locked by the global lock instead) */
int authenticated; /* Needed when the default user requires auth. */
int replstate; /* Replication state if this is a slave. */
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
int replstate; /* Replication state if this is a replica. */
int repl_put_online_on_ack; /* Install replica write handler on ACK. */
int repldbfd; /* Replication DB file descriptor. */
off_t repldboff; /* Replication DB file offset. */
off_t repldbsize; /* Replication DB file size. */
@@ -1217,10 +1298,10 @@ typedef struct client {
long long read_reploff; /* Read replication offset if this is a master. */
long long reploff; /* Applied replication offset if this is a master. */
long long reploff_skipped; /* Repl backlog we did not send to this client */
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a slave. */
long long repl_ack_off; /* Replication ack offset, if this is a replica. */
long long repl_ack_time;/* Replication ack time, if this is a replica. */
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
copying this slave output buffer
copying this replica output buffer
should use. */
char replid[CONFIG_RUN_ID_SIZE+1]; /* Master replication ID (if master). */
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
@@ -1257,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 {
@@ -1398,9 +1480,11 @@ typedef struct rdbSaveInfo {
char repl_id[CONFIG_RUN_ID_SIZE+1]; /* Replication ID. */
long long repl_offset; /* Replication offset. */
int fForceSetKey;
uint64_t mvccMinThreshold;
struct redisMaster *mi;
} rdbSaveInfo;
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1, TRUE}
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1, TRUE, 0, nullptr}
struct malloc_stats {
size_t zmalloc_used;
@@ -1434,6 +1518,7 @@ struct redisServerThreadVars {
aeEventLoop *el;
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
int ipfd_count; /* Used slots in ipfd[] */
int clients_paused; /* True if clients are currently paused */
std::vector<client*> clients_pending_write; /* There is to write or install handler. */
list *unblocked_clients; /* list of clients to unblock before next loop NOT THREADSAFE */
list *clients_pending_asyncwrite;
@@ -1443,9 +1528,10 @@ struct redisServerThreadVars {
client blocked on a module command needs
to be processed. */
client *lua_client = nullptr; /* The "fake client" to query Redis from Lua */
struct fastlock lockPendingWrite;
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 {
@@ -1461,7 +1547,7 @@ struct redisMaster {
char master_replid[CONFIG_RUN_ID_SIZE+1]; /* Master PSYNC runid. */
long long master_initial_offset; /* Master PSYNC offset. */
int repl_state; /* Replication status if the instance is a slave */
int repl_state; /* Replication status if the instance is a replica */
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
@@ -1473,6 +1559,9 @@ struct redisMaster {
unsigned char master_uuid[UUID_BINARY_LEN]; /* Used during sync with master, this is our master's UUID */
/* After we've connected with our master use the UUID in g_pserver->master */
uint64_t mvccLastSync;
/* During a handshake the server may have stale keys, we track these here to share once a reciprocal connection is made */
std::map<int, std::vector<robj_sharedptr>> *staleKeyMap;
};
// Const vars are not changed after worker threads are launched
@@ -1497,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;
@@ -1518,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 {
@@ -1526,7 +1617,7 @@ struct redisServer {
int config_hz; /* Configured HZ value. May be different than
the actual 'hz' field value if dynamic-hz
is enabled. */
int hz; /* serverCron() calls frequency in hertz */
std::atomic<int> hz; /* serverCron() calls frequency in hertz */
redisDb *db;
dict *commands; /* Command table */
dict *orig_commands; /* Command table before command renaming. */
@@ -1561,7 +1652,6 @@ struct redisServer {
list *clients_to_close; /* Clients to close asynchronously */
list *slaves, *monitors; /* List of slaves and MONITORs */
rax *clients_index; /* Active clients dictionary by client ID. */
int clients_paused; /* True if clients are currently paused */
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
std::atomic<uint64_t> next_client_id; /* Next client unique ID. Incremental. */
@@ -1652,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 */
@@ -1671,7 +1762,7 @@ struct redisServer {
int lastbgsave_status; /* C_OK or C_ERR */
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */
int rdb_pipe_read_result_from_child; /* of each replica in diskless SYNC. */
/* Pipe and data structures for child -> parent info sharing. */
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
struct {
@@ -1691,8 +1782,8 @@ struct redisServer {
char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/
long long master_repl_offset; /* My current replication offset */
long long second_replid_offset; /* Accept offsets up to this for replid2. */
int slaveseldb; /* Last SELECTed DB in replication output */
int repl_ping_slave_period; /* Master pings the slave every N seconds */
int replicaseldb; /* Last SELECTed DB in replication output */
int repl_ping_slave_period; /* Master pings the replica every N seconds */
char *repl_backlog; /* Replication backlog for partial syncs */
long long repl_backlog_size; /* Backlog circular buffer size */
long long repl_backlog_histlen; /* Backlog actual data length */
@@ -1709,7 +1800,7 @@ struct redisServer {
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */
int repl_diskless_sync; /* Send RDB to slaves sockets directly. */
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
/* Replication (slave) */
/* Replication (replica) */
list *masters;
int enable_multimaster;
int repl_timeout; /* Timeout after N seconds of master idle */
@@ -1749,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;
@@ -1780,7 +1871,7 @@ struct redisServer {
int cluster_slave_validity_factor; /* Slave max data age for failover. */
int cluster_require_full_coverage; /* If true, put the cluster down if
there is at least an uncovered slot.*/
int cluster_slave_no_failover; /* Prevent slave from starting a failover
int cluster_slave_no_failover; /* Prevent replica from starting a failover
if the master is in failure state. */
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
int cluster_announce_port; /* base port to announce on cluster bus. */
@@ -1825,8 +1916,6 @@ struct redisServer {
int fActiveReplica; /* Can this replica also be a master? */
struct fastlock flock;
// Format:
// Lower 20 bits: a counter incrementing for each command executed in the same millisecond
// Upper 44 bits: mstime (least significant 44-bits) enough for ~500 years before rollover from date of addition
@@ -2049,9 +2138,10 @@ const char *getClientTypeName(int cclass);
void flushSlavesOutputBuffers(void);
void disconnectSlaves(void);
void disconnectSlavesExcept(unsigned char *uuid);
int listenToPort(int port, int *fds, int *count, int fReusePort);
int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListen);
void pauseClients(mstime_t duration);
int clientsArePaused(void);
void unpauseClientsIfNecessary();
int processEventsWhileBlocked(int iel);
int handleClientsWithPendingWrites(int iel);
int clientHasPendingReplies(client *c);
@@ -2164,6 +2254,7 @@ int getLongLongFromObjectOrReply(client *c, robj *o, long long *target, const ch
int getDoubleFromObjectOrReply(client *c, robj *o, double *target, const char *msg);
int getDoubleFromObject(const robj *o, double *target);
int getLongLongFromObject(robj *o, long long *target);
int getUnsignedLongLongFromObject(robj *o, uint64_t *target);
int getLongDoubleFromObject(robj *o, long double *target);
int getLongDoubleFromObjectOrReply(client *c, robj *o, long double *target, const char *msg);
const char *strEncoding(int encoding);
@@ -2203,9 +2294,10 @@ void replicationSendNewlineToMaster(struct redisMaster *mi);
long long replicationGetSlaveOffset(struct redisMaster *mi);
char *replicationGetSlaveName(client *c);
long long getPsyncInitialOffset(void);
int replicationSetupSlaveForFullResync(client *slave, long long offset);
int replicationSetupSlaveForFullResync(client *replica, long long offset);
void changeReplicationId(void);
void clearReplicationId2(void);
void mergeReplicationId(const char *);
void chopReplicationBacklog(void);
void replicationCacheMasterUsingMyself(struct redisMaster *mi);
void feedReplicationBacklog(const void *ptr, size_t len);
@@ -2442,6 +2534,7 @@ int rewriteConfig(char *path);
/* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key);
int removeExpireCore(redisDb *db, robj *key, dictEntry *de);
int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey);
void propagateExpire(redisDb *db, robj *key, int lazy);
int expireIfNeeded(redisDb *db, robj *key);
expireEntry *getExpire(redisDb *db, robj_roptr key);
@@ -2642,6 +2735,7 @@ void monitorCommand(client *c);
void expireCommand(client *c);
void expireatCommand(client *c);
void expireMemberCommand(client *c);
void expireMemberAtCommand(client *c);
void pexpireCommand(client *c);
void pexpireatCommand(client *c);
void getsetCommand(client *c);
@@ -2727,7 +2821,6 @@ void evalShaCommand(client *c);
void scriptCommand(client *c);
void timeCommand(client *c);
void bitopCommand(client *c);
void bitopRangeCommand(client *c);
void bitcountCommand(client *c);
void bitposCommand(client *c);
void replconfCommand(client *c);
@@ -2803,7 +2896,7 @@ void xorDigest(unsigned char *digest, const void *ptr, size_t len);
int populateCommandTableParseFlags(struct redisCommand *c, const char *strflags);
int moduleGILAcquiredByModule(void);
extern bool g_fInCrash;
extern int g_fInCrash;
static inline int GlobalLocksAcquired(void) // Used in asserts to verify all global locks are correctly acquired for a server-thread to operate
{
return aeThreadOwnsLock() || moduleGILAcquiredByModule() || g_fInCrash;
@@ -2829,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
@@ -33,7 +33,7 @@
/* ----------------- Blocking sockets I/O with timeouts --------------------- */
/* Redis performs most of the I/O in a nonblocking way, with the exception
* of the SYNC command where the slave does it in a blocking way, and
* of the SYNC command where the replica does it in a blocking way, and
* the MIGRATE command that must be blocking in order to be atomic from the
* point of view of the two instances (one migrating the key and one receiving
* the key). This is why need the following blocking I/O functions.
+1 -1
View File
@@ -803,7 +803,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
* RETRYCOUNT <count> FORCE JUSTID LASTID <id>.
*
* Note that JUSTID is useful in order to avoid that XCLAIM will do
* useless work in the slave side, trying to fetch the stream item. */
* useless work in the replica side, trying to fetch the stream item. */
robj *argv[14];
argv[0] = createStringObject("XCLAIM",6);
argv[1] = key;
+1 -1
View File
@@ -1,3 +1,3 @@
#define KEYDB_REAL_VERSION "0.0.0"
#define KEYDB_REAL_VERSION "5.3.3"
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
+2
View File
@@ -14,6 +14,8 @@ proc main {} {
spawn_instance redis $::redis_base_port $::instances_count {
"cluster-enabled yes"
"appendonly yes"
"testmode yes"
"server-threads 3"
}
run_tests
cleanup
+1 -1
View File
@@ -6,7 +6,7 @@ if {$system_name eq {linux} || $system_name eq {darwin}} {
test "Server is able to generate a stack trace on selected systems" {
r config set watchdog-period 200
r debug sleep 1
set pattern "*debugCommand*"
set pattern "*watchdogSignalHandler*"
set retry 10
while {$retry} {
set result [exec tail -100 < [srv 0 stdout]]
+52
View File
@@ -9,6 +9,7 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
set master [srv 0 client]
set master_host [srv 0 host]
set master_port [srv 0 port]
set master_pid [s process_id]
# Use a short replication timeout on the slave, so that if there
# are no bugs the timeout is triggered in a reasonable amount
@@ -58,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
@@ -93,5 +112,38 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
assert_equal {0} [$master del testkey1]
assert_equal {0} [$slave del testkey1]
}
test {Active replica expire propogates when source is down} {
$slave flushall
$slave set testkey2 foo
$slave set testkey1 foo
wait_for_condition 50 1000 {
[string match *foo* [$master get testkey1]]
} else {
fail "Replication failed to propogate"
}
$slave expire testkey1 2
assert_equal {1} [$slave wait 1 500] { "value should propogate
within 0.5 seconds" }
exec kill -SIGSTOP $slave_pid
after 3000
# Ensure testkey1 is gone. Note, we can't do this directly as the normal commands lie to us
# about what is actually in the dict. The only way to know is with a count from info
assert_equal {1} [expr [string first {keys=1} [$master info keyspace]] >= 0] {"slave expired"}
}
exec kill -SIGCONT $slave_pid
test {Active replica different databases} {
$master select 3
$master set testkey abcd
$master select 2
$master del testkey
$slave select 3
wait_for_condition 50 1000 {
[string match abcd [$slave get testkey]]
} else {
fail "Replication failed to propogate DB 3"
}
}
}
}
@@ -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"}} {
}
}
}
+4
View File
@@ -35,6 +35,9 @@ set ::all_tests {
unit/quit
unit/aofrw
unit/acl
unit/rreplay
unit/cron
unit/replication
integration/block-repl
integration/replication
integration/replication-2
@@ -42,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
+58
View File
@@ -214,6 +214,64 @@ start_server {tags {"bitops"}} {
r bitop or x a b
} {32}
test {BITOP lshift size} {
r set a " "
r bitop lshift x a 1
} {2}
test {BITOP rshift size} {
r set a " "
r bitop rshift x a 1
} {1}
test {BITOP rshift 0 byte} {
r set a " "
r bitop rshift x a 8
} {0}
test {BITOP rshift underflow} {
r set a " "
r bitop rshift x a 65
} {0}
test {BITOP lshift string} {
r set a "abcdefg"
r bitop lshift x a 8
r get x
} "\000abcdefg"
test {BITOP lshift char} {
r set a "\xAA"
r bitop lshift x a 4
r get x
} "\xA0\x0A"
test {BITOP rshift char} {
r set a "\xAA"
r bitop rshift x a 3
r get x
} "\x15"
test {BITOP lshift carry} {
r set a "\xFF"
r bitop lshift x a 1
r get x
} "\xFE\x01"
test {BITOP rshift carry} {
r set a "\x00\xFF"
r bitop rshift x a 1
r get x
} "\x80\x7F"
test {BITOP rshift reciprocal} {
r flushdb
r set a "abcdefg"
r bitop lshift b a 14
r bitop rshift res b 14
r get res
} "abcdefg\x00"
test {BITPOS bit=0 with empty key returns 0} {
r del str
r bitpos str 0
+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
}
}
+30
View File
@@ -0,0 +1,30 @@
start_server {tags {"rreplay"} overrides {active-replica yes}} {
test {RREPLAY use current db} {
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"
r get dbnum
} {four}
reconnect
test {RREPLAY db different} {
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
r select 4
assert { [r get testkey] == "four" }
r select 2
r get testkey
} {bebe}
reconnect
test {RREPLAY not master} {
assert_error "*master*" {r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$7\r\ntestkey\r\n\$4\r\nbebe\r\n" 2}
}
r flushdb
}
+2 -2
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"
@@ -202,7 +202,7 @@ REDIS_CHKCONFIG_INFO=\
# chkconfig: - 58 74\n
# description: redis_${REDIS_PORT} is the redis daemon.\n
### BEGIN INIT INFO\n
# Provides: redis_6379\n
# Provides: redis_${REDIS_PORT}\n
# Required-Start: \$network \$local_fs \$remote_fs\n
# Required-Stop: \$network \$local_fs \$remote_fs\n
# Default-Start: 2 3 4 5\n