Compare commits

...
358 Commits
Author SHA1 Message Date
NotAlexNoyle a8bd2a1c7c Exclude CODEOWNERS from language statistics 2026-10-31 16:52:19 -04:00
NotAlexNoyle 99efa05aa6 Add CODEOWNERS (one staff approval) 2026-10-31 16:52:19 -04:00
NotAlexNoyle 7e66e5e1f0 Update rocksdb 2026-10-08 17:31:07 -04:00
NotAlexNoyle b987cb2c5c Update rocksdb 2026-09-16 11:41:04 -04:00
NotAlexNoyle 1a61e306e9 Update rocksdb 2026-09-14 18:28:02 -04:00
NotAlexNoyle eff3665180 Update core 2026-09-12 09:47:26 -04:00
NotAlexNoyle ccbcbe7229 Update rocksdb 2026-09-10 18:23:27 -04:00
NotAlexNoyle 59dd3664b6 Update rocksdb 2026-09-10 13:14:46 -04:00
NotAlexNoyle 31e30a32cc Update rocksdb 2026-08-24 10:38:08 -04:00
NotAlexNoyle a57c4e079f Update rocksdb 2026-08-19 10:01:56 -04:00
NotAlexNoyle ec7b614c48 Update rocksdb 2026-08-15 10:13:08 -04:00
NotAlexNoyle 9a0bdef8e6 Update rocksdb 2026-08-13 16:25:48 -04:00
NotAlexNoyle 950d06bb84 Update rocksdb 2026-08-11 23:26:07 -04:00
NotAlexNoyle 61503be895 Update rocksdb 2026-08-08 10:47:30 -04:00
NotAlexNoyle 6eac08f2fe Update rocksdb 2026-08-07 13:01:24 -04:00
NotAlexNoyle 71f35897ee Update rocksdb 2026-08-05 17:09:39 -04:00
NotAlexNoyle 03c2edc004 Update rocksdb 2026-08-05 12:25:10 -04:00
NotAlexNoyle dee8cb4028 Update rocksdb 2026-08-03 10:04:48 -04:00
NotAlexNoyle de84d8044e Update rocksdb 2026-07-31 13:31:09 -04:00
NotAlexNoyle b0df698af8 Update rocksdb 2026-07-30 14:52:32 -04:00
NotAlexNoyle 549282ecc9 Update rocksdb 2026-07-29 17:30:59 -04:00
NotAlexNoyle fdd5f3ce4b Update rocksdb 2026-07-29 16:51:45 -04:00
NotAlexNoyle 63bab06751 Update rocksdb 2026-07-29 11:20:42 -04:00
NotAlexNoyle 3e9050b112 Update rocksdb 2026-07-27 06:51:58 -04:00
NotAlexNoyle 7606dd0c70 Update rocksdb 2026-07-26 09:15:05 -04:00
NotAlexNoyle 4886e81614 Update rocksdb 2026-07-24 12:49:08 -04:00
NotAlexNoyle 4e02efca33 Update rocksdb 2026-07-23 18:56:23 -04:00
NotAlexNoyle b517ef5c37 Update rocksdb 2026-07-22 19:48:18 -04:00
NotAlexNoyle 7659fa081d Update rocksdb 2026-07-22 13:45:30 -04:00
NotAlexNoyle e79bbecca8 Update rocksdb 2026-07-22 06:59:55 -04:00
NotAlexNoyle 4a4d6067d8 Update rocksdb 2026-07-20 14:47:34 -04:00
NotAlexNoyle 8f37fde778 Update rocksdb 2026-07-18 08:12:56 -04:00
NotAlexNoyle 33120b579d Update rocksdb 2026-07-17 15:58:36 -04:00
NotAlexNoyle 2fed2cba2b Update rocksdb 2026-07-17 12:22:24 -04:00
NotAlexNoyle 6ed8a95602 Update keydb 2026-07-16 09:49:07 -04:00
NotAlexNoyle 043a9c2a16 Update rocksdb 2026-07-15 15:40:22 -04:00
NotAlexNoyle 5cc82eb2b4 Update rocksdb 2026-07-15 10:23:50 -04:00
NotAlexNoyle 9a7c6b01ae Update rocksdb 2026-07-12 17:06:04 -04:00
NotAlexNoyle 14fd71bfc3 Update rocksdb 2026-07-09 13:05:12 -04:00
NotAlexNoyle 32197cf80c Update keydb 2026-07-08 08:50:01 -04:00
NotAlexNoyle 93a2f73b30 Update rocksdb 2026-07-03 08:05:40 -04:00
NotAlexNoyle 7557cfb185 Update rocksdb 2026-07-02 09:58:51 -04:00
NotAlexNoyle 929d7553fa Update rocksdb 2026-07-01 15:40:52 -04:00
NotAlexNoyle 580f97e4bf Update rocksdb 2026-07-01 08:45:12 -04:00
NotAlexNoyle 27a1ad3181 Update rocksdb 2026-06-25 17:39:53 -04:00
NotAlexNoyle 492175643c Update rocksdb 2026-06-25 16:46:59 -04:00
NotAlexNoyle 4265629274 Update rocksdb 2026-06-24 18:00:48 -04:00
NotAlexNoyle 53d018d35b Update core 2026-06-24 16:48:05 -04:00
NotAlexNoyle e4d7cbc8a1 Update core 2026-06-24 11:40:18 -04:00
NotAlexNoyle 5e0268f1e4 Update rocksdb 2026-06-21 08:53:03 -04:00
NotAlexNoyle 1bd351bc8f Update rocksdb 2026-06-18 14:54:39 -04:00
NotAlexNoyle a2944bca1a Update rocksdb 2026-06-17 05:40:32 -04:00
NotAlexNoyle 42e515e7bf Update rocksdb 2026-06-16 06:00:40 -04:00
NotAlexNoyle fcfd25190c Update rocksdb 2026-06-15 21:54:28 -04:00
NotAlexNoyle a3668ba633 Update rocksdb 2026-06-13 10:50:31 -04:00
NotAlexNoyle 2c9980d8b9 Update rocksdb 2026-06-06 08:25:15 -04:00
NotAlexNoyle c25b9805e6 Update rocksdb 2026-06-05 09:37:54 -04:00
NotAlexNoyle b2935a09e7 Update rocksdb 2026-06-04 14:36:20 -04:00
NotAlexNoyle fc1630907f Update rocksdb 2026-06-03 18:00:41 -04:00
NotAlexNoyle 267907bb1b Update rocksdb 2026-06-01 19:53:17 -04:00
NotAlexNoyle 4ecb36916c Update rocksdb 2026-05-31 10:58:35 -04:00
NotAlexNoyle 3b98c3938f Update rocksdb 2026-05-27 08:31:24 -04:00
NotAlexNoyle 0c7f0a7a6f Update rocksdb 2026-05-27 06:53:25 -04:00
NotAlexNoyle f4dd4f5a45 Update rocksdb 2026-05-25 10:40:53 -04:00
NotAlexNoyle 908f4b944a Update rocksdb 2026-05-24 12:30:42 -04:00
NotAlexNoyle 8e779217f8 Update keydb 2026-05-21 10:57:11 -04:00
NotAlexNoyle 9efdf0975d Update keydb 2026-05-20 05:06:09 -04:00
NotAlexNoyle 795adc2e09 Update rocksdb 2026-05-17 22:44:18 -04:00
NotAlexNoyle 610516a655 Update rocksdb 2026-05-15 14:23:16 -04:00
NotAlexNoyle c767eb91b7 Update rocksdb 2026-05-15 11:41:48 -04:00
NotAlexNoyle 1b9e23050e Update rocksdb 2026-05-14 17:47:02 -04:00
NotAlexNoyle 360ab1cdf4 Update rocksdb 2026-05-11 15:19:55 -04:00
NotAlexNoyle 8a3e982934 Update KeyDB 2026-05-11 02:48:00 -04:00
NotAlexNoyle 08363f1213 Update rocksdb 2026-05-07 14:55:35 -04:00
NotAlexNoyle b4060b4a12 Update rocksdb 2026-05-07 14:12:58 -04:00
NotAlexNoyle 72f96aea86 Update rocks 2026-05-07 10:03:14 -04:00
NotAlexNoyle 2d8a0ad398 Update rocksdb 2026-05-06 00:09:39 -04:00
NotAlexNoyle d30dfe2ece Update rocksdb 2026-05-04 02:13:59 -04:00
NotAlexNoyle 0b52f72ba6 Update rocksdb 2026-04-30 09:19:08 -04:00
NotAlexNoyle f979407963 Update rocksdb 2026-04-28 09:34:29 -04:00
NotAlexNoyle cc02e466df Update rocksdb 2026-04-24 09:15:03 -04:00
NotAlexNoyle a369c73a1e Update rocksdb 2026-04-23 09:55:06 -04:00
NotAlexNoyle a56ab2a9b2 Update rocksdb 2026-04-22 13:14:15 -04:00
NotAlexNoyle 1626da9f21 Update rocksdb 2026-04-22 07:09:05 -04:00
NotAlexNoyle 686900d570 Update rocksdb 2026-04-21 10:56:13 -04:00
NotAlexNoyle 489d68389e Update rocksdb 2026-04-20 01:47:24 -04:00
NotAlexNoyle 8299d138f6 Update rocksdb 2026-04-15 07:07:00 -04:00
NotAlexNoyle 0b5d2869bb Update rocksdb 2026-04-14 14:29:27 -04:00
NotAlexNoyle 9f03c660dd Update rocksdb 2026-04-11 12:57:42 -04:00
NotAlexNoyle 0e66e5026e Update rocksdb 2026-04-10 15:29:09 -04:00
NotAlexNoyle 06184e9a76 Update rocksdb 2026-04-09 15:00:48 -04:00
NotAlexNoyle 687ff95ffd Update rocksdb 2026-04-08 06:16:06 -04:00
NotAlexNoyle 8ddbe5cf31 Update rocksdb 2026-04-04 14:13:09 -04:00
NotAlexNoyle 709cbf6bcb Update rocksdb 2026-04-01 15:32:31 -04:00
NotAlexNoyle 42cdb214fe Update rocksdb 2026-04-01 04:59:33 -04:00
NotAlexNoyle 7182d64657 Update rocksdb 2026-03-31 10:07:36 -04:00
NotAlexNoyle 8b2a924044 Update rocksdb 2026-03-29 10:08:09 -04:00
NotAlexNoyle 56ffc8bab0 Update rocksdb 2026-03-27 11:06:17 -04:00
NotAlexNoyle 39b63bbe0a Update rocksdb 2026-03-26 12:04:52 -04:00
NotAlexNoyle e7d9c1659a Update rocksdb 2026-03-25 13:07:00 -04:00
NotAlexNoyle 281238cecf Update rocksdb 2026-03-25 03:14:44 -04:00
NotAlexNoyle e78d7a62db Update rocksdb 2026-03-23 13:57:25 -04:00
NotAlexNoyle b501ced0b4 Update rocksdb 2026-03-23 04:04:58 -04:00
NotAlexNoyle e1a4aea5b8 Update KeyDB 2026-03-18 05:10:12 -04:00
NotAlexNoyle 407524f72d Update rocksdb 2026-03-17 10:25:51 -04:00
NotAlexNoyle c7022494dc Update rocksdb 2026-03-16 15:23:55 -04:00
NotAlexNoyle e54b62fe56 Update rocksdb 2026-03-16 01:22:02 -04:00
NotAlexNoyle d4eb65d03f Update rocksdb 2026-03-13 09:47:02 -04:00
NotAlexNoyle 15eba15c9d Update rocksdb 2026-03-12 12:51:02 -04:00
NotAlexNoyle 6e33192e41 Update rocksdb 2026-03-11 16:39:18 -04:00
NotAlexNoyle 90988846fe Update rocksdb 2026-03-11 00:32:50 -04:00
NotAlexNoyle 2b8224d753 Update rocksdb 2026-03-09 06:50:24 -04:00
NotAlexNoyle df3d89f112 Update rocksdb 2026-03-02 06:58:51 -05:00
NotAlexNoyle 867431c4d4 Update rocksdb 2026-02-09 06:17:07 -05:00
NotAlexNoyle e553ef1533 Update rocksdb 2026-02-04 02:21:06 -05:00
NotAlexNoyle 6ebbc3018e Update rocksdb 2026-01-27 22:35:23 -05:00
NotAlexNoyle 266bfb763d Update rocksdb 2026-01-26 08:20:33 -05:00
NotAlexNoyle 4bc300e14c Update rocksdb 2026-01-21 13:38:21 -05:00
NotAlexNoyle e71ef0c02a Update rocksdb 2026-01-21 05:44:20 -05:00
NotAlexNoyle ce70093cfc Update rocksdb 2026-01-19 13:38:34 -05:00
NotAlexNoyle 28c6a9715b Update rocksdb 2025-12-26 07:07:18 -05:00
NotAlexNoyle b9bbc6fbf3 Update rocksdb 2025-12-23 10:01:05 -05:00
NotAlexNoyle 9dc9163e57 Update rocksdb 2025-12-17 09:08:22 -05:00
NotAlexNoyle a705d431c1 update rocksdb 2025-12-13 12:18:58 -05:00
NotAlexNoyle ccb95df616 Update rocksdb 2025-12-07 20:08:56 -05:00
NotAlexNoyle 4275b54b1a Update rocksdb 2025-12-02 21:54:36 -05:00
NotAlexNoyle 0ee0609402 Update rocksdb dependency 2025-11-28 09:35:07 -05:00
michael-grunder 603ebb27fb I think we want ARM's program counter here.
Fixes #797
2024-04-04 16:26:33 -04:00
Guillem Jover 0731a0509a build: Add support for linking against a system libhiredis/libhiredis_ssl
Add a new USE_SYSTEM_HIREDIS make variable to select whether to link
against the system libhiredis (and libhiredis_ssl if BUILD_TLS is
enabled).

Move the sdscompat.h header from the vendored hiredis directory to src/,
as this file is not and has never been part of the upstream hiredis
project, it got added in commit bffbbeaa9a
in redis itself.
2024-03-25 21:01:05 -04:00
Guillem Jover d67c65867a build: Add support for linking against a system librocksdb
Add a new USE_SYSTEM_ROCKSDB make variable to select whether to link
against the system librocksdb.
2024-03-25 21:01:05 -04:00
Guillem Jover ae130d1a7f build: Add support for linking against a system libjemalloc
Add a new USE_SYSTEM_JEMALLOC make variable to select whether to link
against the system libjemalloc.
2024-03-25 21:01:05 -04:00
Guillem Jover 10c63d743c build: Add support for using the system concurrentqueue headers
Add a new USE_SYSTEM_CONCURRENTQUEUE make variable to select whether
to include the system concurrentqueue headers.
2024-03-25 21:01:05 -04:00
Ikko Eltociear Ashimine 5e643e9a9c Update README.md
intented -> intended
2024-03-25 20:58:26 -04:00
Teemu Kekkonen 2cb5d71073 Update RPM script for Rocky Linux 9
This uses the 'dist' RPM macro instead of relying on the version information of
CentOS packages.  This approach works on CentOS 7 and 8 as well.
2024-03-07 19:54:09 -05:00
Tibault Damman 011fdb4772 fix build on 32bit systems
Code had an assert that tests if sizeof(long) == sizeof(long long),
which obviously fails on 32-bit architectures.
I believe the assert is incorrect on any architecture, considering
the following code is actually putting a long long in an _int_.

I replaced it with code that checks if the value fits in an int.

Signed-off-by: Tibault Damman <tibault.damman@basalte.be>
2024-03-07 19:50:35 -05:00
Roman Donchenko d94fddae0a Add more detail to the panic messages in blocked.cpp 2024-03-07 19:50:00 -05:00
k00809413 2ad14dec88 Fix swapdb bugs when multiple clients involved. 2024-01-10 15:32:51 -05:00
Malavan Sotheeswaran 2abe7086a8 Update README.md (#762) 2023-12-18 15:30:48 -05:00
Malavan Sotheeswaran 674d9fb7ee stat64 is deprecated on mac (#737) 2023-11-16 03:05:43 -05:00
Malavan Sotheeswaran de3c218a02 add release notes up to 6.3.4 2023-10-29 17:39:55 -07:00
Malavan Sotheeswaran 05945b60a8 missed a refernce to isRocksdbSnapshotRepl 2023-10-26 11:40:50 -07:00
Malavan Sotheeswaran 486a6bb7b1 Merge remote-tracking branch 'internal/main' 2023-10-26 11:21:46 -07:00
John Sully 70503f6b58 Disable fastsync by default 2023-10-23 15:23:11 -04:00
John Sully 46615ac099 Fix test 2023-10-23 15:23:11 -04:00
John Sully a7ac6c1e43 Remove unused variable 2023-10-23 15:23:11 -04:00
John Sully d7977c468f Fastsync in-mem initial implementation 2023-10-23 15:23:11 -04:00
John Sully 7ae27f61f0 rename constants 2023-10-23 15:23:11 -04:00
Malavan Sotheeswaran 47f635ee16 Fix tls auditlog tests and enable tls for CI (#234)
* fix auditlog test titles, and enable tls tests for CI

* fix tls auditlog key string
2023-10-23 14:34:23 -04:00
Malavan Sotheeswaran 79b0c84710 Fixing some compile issues for alpine and centos, also fix endian issue for FLASH hashslot prefix (#722)
*fix for rocksdb centos compile fail

* fix alpine compile

* fix hashslot enumerate

* update alpine docker to latest version

* fix mac build, use big endian for expire prefix

* use endian.h for expires too
2023-10-13 15:00:35 -04:00
John Sully cd42776c9f Make the connect timeout configurable when in cluster mode to prevent huge delays 2023-10-12 12:11:18 -04:00
Einar Fløystad Dørum 7049b5f4e8 Add KEYDB_PASSWORD env variable to Dockerfiles (#220) (#704) 2023-09-28 21:03:45 -04:00
Karthick AriyaratnamandKarthick Ariyaratnam 5b12251627 Fix integer overflow issue in the temp rdb file naming. (#702)
Co-authored-by: Karthick Ariyaratnam (A) <k00809413@china.huawei.com>
2023-09-28 21:01:47 -04:00
michieldwitteandMichiel De Witte a0f280e1a2 rename zset to iterzset (#706)
Co-authored-by: Michiel De Witte <michiel.dewitte@basalte.be>
2023-09-28 21:01:05 -04:00
57f4a2e8b1 Fix a bug where flash CF options are being reset to default after flushall. (#718)
Co-authored-by: Karthick Ariyaratnam (A) <k00809413@china.huawei.com>
Co-authored-by: Malavan Sotheeswaran <105669860+msotheeswaran-sc@users.noreply.github.com>
2023-09-28 21:00:28 -04:00
38169682f0 Merge latest internal to OSS (#720)
* add docker build

* fix the working dir in Dockerfile

* add release publish docker image

* address intentation and use default release

* migrate keydb_modstatsd to keydb-internal

* rm

* add submodule cpp-statsd-client

* include trigger keydb_modstatsd Makefile in modules Makefile

* update

* have clean also trigger keydb_modstatsd clean

* move cpp-statsd-client to deps

* checkout to a06a5b9359f31d946fe163b9038586982971ae49

* update relative path in compilation

* remove submodule instead use the source

* include building keydb statsd module

* fix check in Dockerfile docker-entrypoint.sh

* fix

* fix the comment caused stuck docker build

* use commit hash as tag template

* fix

* test tag

* Revert "test tag"

This reverts commit 9cbc57137d.

* make docker build independent

* add new build to ci

* emit system free metrics with '/proc/meminfo'

* have emit system free memory within metrics_time_taken_us and also add metric time taken for it

* Remove Expireset (#217)

Major refactor to place expiry information directly in the object struct.

* update MemFree to MemAvailable in keydb statsd

* add metric emit for non-empty primary with less than 2 connected replicas

* address comments

* Multiply CPU percent metric by 100

* Fix memory leaks

* Fix slow to free when low lock contention

* fix nodename metricsname

* fix unnecessary replace

* Make propagating before freeing module context optional (#225)

* don't propogate on module context free for rdb load

* default in wrong place

* Flash expiration (#197)

Design Doc: https://docs.google.com/document/d/1NmnYGnHLdZp-KOUCUatX5iXpF-L3YK4VUc9Lm3Tqxpo/edit?usp=sharing

* Emit more aggregate metrics in modstatsd (#223)

* Permit keys of differing slots as long as they are served by this cluster and we are not migrating

* Fix over pessimistic checks that prevent replicas from serving mget

* Fix logic bug

* async rehash is preventing rehashing during RDB load after a db flush.  Ensure it can't interefere after a flush

* make async rehash configurable

* only use internal locks when multithreaded (#205)

* Fix crossslot error migrating batches of keys

* Fix bug where we erroneously answer queries belonging to another shard

* fix mac compile

* enable level_compaction_dynamic_level_bytes after flush, and flush expires for FLASH (#229)

* enable level_compaction_dynamic_level_bytes after flush, and flush expires

* update debug reload for flash

* update debug reload for flash complete

* missing forward declare

* commit existing changes then track changes for debug reload

* missing args

* commitChanges is conditional

Co-authored-by: John Sully <jsully@snapchat.com>

---------

Co-authored-by: zliang <zliang@snapchat.com>
Co-authored-by: John Sully <jsully@snapchat.com>
Co-authored-by: Alex Cope <acope@snapchat.com>
Co-authored-by: John Sully <john@csquare.ca>
2023-09-28 18:13:27 -04:00
Malavan SotheeswaranandJohn Sully dfcc848e1c enable level_compaction_dynamic_level_bytes after flush, and flush expires for FLASH (#229)
* enable level_compaction_dynamic_level_bytes after flush, and flush expires

* update debug reload for flash

* update debug reload for flash complete

* missing forward declare

* commit existing changes then track changes for debug reload

* missing args

* commitChanges is conditional

Co-authored-by: John Sully <jsully@snapchat.com>
2023-09-28 16:59:23 -04:00
John Sully e7f90f0096 Fix bug where we erroneously answer queries belonging to another shard 2023-09-27 07:38:09 +00:00
John Sully 025b7077f7 Fix crossslot error migrating batches of keys 2023-09-26 00:11:21 -04:00
Malavan Sotheeswaran 596c513d3e only use internal locks when multithreaded (#205) 2023-09-25 15:53:47 -04:00
John Sully 570bcb55c7 make async rehash configurable 2023-09-23 16:32:28 -04:00
John Sully 68d751046e async rehash is preventing rehashing during RDB load after a db flush. Ensure it can't interefere after a flush 2023-09-23 16:32:28 -04:00
John Sully cfe257a9c7 Fix logic bug 2023-09-20 12:57:10 -07:00
John Sully c6e20ea396 Fix over pessimistic checks that prevent replicas from serving mget 2023-09-20 12:57:10 -07:00
a00817524 5f5753ae53 build failure 2023-09-17 19:06:00 -04:00
a00817524 e42d83d208 build failure 2023-09-17 19:06:00 -04:00
a00817524 9c32d4c1c8 removed proc call 2023-09-17 19:06:00 -04:00
a00817524 337eb297a1 review rework 2023-09-17 19:06:00 -04:00
a00817524 cf13e7b594 Async FLASH Design: AE event loop changes to understand StorageToken 2023-09-17 19:06:00 -04:00
John Sully 9251f1c244 Permit keys of differing slots as long as they are served by this cluster and we are not migrating 2023-09-14 21:55:04 -04:00
Zhaozhen Liang 7b7c0ba192 Merge pull request #224 from Snapchat/remove-dot-in-nodename-metrics
fix nodename metricsname
2023-09-11 15:37:16 -06:00
zliang 98e306c59b Merge branch 'main' into remove-dot-in-nodename-metrics 2023-09-11 12:18:39 -06:00
Alex Cope a55c687cd0 Emit more aggregate metrics in modstatsd (#223) 2023-09-11 10:43:25 -07:00
Malavan Sotheeswaran ace783a00e Flash expiration (#197)
Design Doc: https://docs.google.com/document/d/1NmnYGnHLdZp-KOUCUatX5iXpF-L3YK4VUc9Lm3Tqxpo/edit?usp=sharing
2023-09-08 16:25:53 -04:00
Malavan Sotheeswaran 7d4f461562 Make propagating before freeing module context optional (#225)
* don't propogate on module context free for rdb load

* default in wrong place
2023-09-08 15:51:48 -04:00
zliang e31533bd7a fix unnecessary replace 2023-08-31 12:45:42 -06:00
zliang c20af57cf1 fix nodename metricsname 2023-08-31 12:29:59 -06:00
John Sully 820482bffe Fix slow to free when low lock contention 2023-08-25 20:40:16 -04:00
John Sully f435218ed9 Fix memory leaks 2023-08-25 16:44:10 +00:00
Alex Cope 8b755aee1b Multiply CPU percent metric by 100 2023-08-22 17:33:57 -04:00
Zhaozhen Liang c97f696ea2 Merge pull request #219 from Snapchat/emit-non-empty-shard-less-replica-issue
add metric emit for non-empty primary with less than 2 connected replicas
2023-08-22 11:34:09 -06:00
zliang cec570fdf1 address comments 2023-08-22 11:10:56 -06:00
zliang 6375e919b9 add metric emit for non-empty primary with less than 2 connected replicas 2023-08-21 18:29:05 -06:00
Zhaozhen Liang bf29926686 Merge pull request #218 from Snapchat/change-memfree-to-memavailable
update MemFree to MemAvailable in keydb statsd
2023-08-21 16:46:38 -06:00
zliang 813bf15ae0 update MemFree to MemAvailable in keydb statsd 2023-08-21 15:28:26 -06:00
John Sully ffac55273a Remove Expireset (#217)
Major refactor to place expiry information directly in the object struct.
2023-08-21 16:36:45 -04:00
Zhaozhen Liang 80dcbad30f Merge pull request #216 from Snapchat/add-free-memory-and-shard-num-metrics-emit-statsd
emit system free memory metrics with '/proc/meminfo'
2023-08-18 10:50:21 -06:00
zliang eb14f4298c have emit system free memory within metrics_time_taken_us and also add metric time taken for it 2023-08-18 10:20:00 -06:00
zliang 6fa696bb98 emit system free metrics with '/proc/meminfo' 2023-08-17 19:31:52 -06:00
Zhaozhen Liang abd6e77c95 Merge pull request #215 from Snapchat/make-docker-build-independent
make docker build independent
2023-08-16 12:24:25 -06:00
zliang 7a00a43170 add new build to ci 2023-08-15 15:52:59 -06:00
zliang 426eabe143 make docker build independent 2023-08-15 15:27:05 -06:00
Zhaozhen Liang 222776e98b Merge pull request #214 from Snapchat/update-docker-build-tag
use commit hash as tag template
2023-08-15 13:13:30 -06:00
zliang 76fec1c22e Revert "test tag"
This reverts commit 9cbc57137d.
2023-08-15 12:40:10 -06:00
zliang 9cbc57137d test tag 2023-08-15 12:25:15 -06:00
zliang 94b40af1c3 fix 2023-08-15 11:56:03 -06:00
zliang df9039d83f use commit hash as tag template 2023-08-15 11:37:29 -06:00
Zhaozhen Liang a1b55eddf8 Merge pull request #211 from Snapchat/machamp-release-publish-image
add docker build
2023-08-15 09:40:43 -06:00
zliang 70f2a74ff1 fix the comment caused stuck docker build 2023-08-14 18:34:05 -06:00
zliang e15a2b609c fix 2023-08-14 17:05:37 -06:00
zliang 7d96689ae6 fix check in Dockerfile docker-entrypoint.sh 2023-08-14 16:31:41 -06:00
Zhaozhen Liang 8cbb53e7cb Merge branch 'main' into machamp-release-publish-image 2023-08-14 12:55:30 -06:00
zliang b6f91e4c26 include building keydb statsd module 2023-08-14 12:03:36 -06:00
Zhaozhen Liang deb48fba62 Merge pull request #213 from Snapchat/move-cpp-statsd-client-to-dep
move cpp-statsd-client to deps
2023-08-14 11:30:40 -06:00
zliang b51f267112 remove submodule instead use the source 2023-08-14 11:13:06 -06:00
zliang a055da3b4c update relative path in compilation 2023-08-14 10:56:51 -06:00
zliang 68ce12ad3a checkout to a06a5b9359f31d946fe163b9038586982971ae49 2023-08-14 10:25:23 -06:00
zliang b8208794a3 move cpp-statsd-client to deps 2023-08-14 10:23:22 -06:00
zliang 12bf6188ae Merge branch 'main' into machamp-release-publish-image 2023-08-11 17:21:51 -06:00
Zhaozhen Liang cb147defbd Merge pull request #212 from Snapchat/migrate-keydb_modstatsd
migrate keydb_modstatsd to keydb-internal
2023-08-11 15:40:41 -06:00
zliang 4d56a1c5ed have clean also trigger keydb_modstatsd clean 2023-08-11 15:19:53 -06:00
zliang 289df10eb1 update 2023-08-11 15:16:10 -06:00
zliang 6e0305828e include trigger keydb_modstatsd Makefile in modules Makefile 2023-08-11 15:14:35 -06:00
zliang 6e1fbb2d14 add submodule cpp-statsd-client 2023-08-11 13:34:23 -06:00
zliang c94560f0ba rm 2023-08-11 13:32:20 -06:00
zliang db3fb7e9fe migrate keydb_modstatsd to keydb-internal 2023-08-11 13:26:28 -06:00
zliang 7a496d3dde address intentation and use default release 2023-08-10 14:08:37 -06:00
zliang 7bce881287 add release publish docker image 2023-08-10 11:29:07 -06:00
zliang 353695887a fix the working dir in Dockerfile 2023-08-09 11:44:33 -06:00
zliang 4fe4b235b2 add docker build 2023-08-08 19:28:44 -06:00
Malavan Sotheeswaran 441b74a936 Merge remote-tracking branch 'origin/main' 2023-07-25 21:44:27 -07:00
Alex Cope ce70e8cd0c revert 2023-07-25 21:35:17 -07:00
Alex Cope e3a1ea3928 with config 2023-07-25 21:34:50 -07:00
Rolf Ahrenberg a464a7173d Use tini in Alpine-based Docker image (#682) 2023-07-25 21:33:30 -07:00
Karthick Ariyaratnam (A) 806175df99 Fix a fug where repl-backlog-size config was modifed in keydb.conf with the runtime value during config rewrite. 2023-07-25 21:33:30 -07:00
Karthick AriyaratnamandKarthick Ariyaratnam bcb542128a Fix a bug where a temp rdb file with zero bytes is generated in flash mode. (#696)
Co-authored-by: Karthick Ariyaratnam (A) <k00809413@china.huawei.com>
2023-07-25 21:33:30 -07:00
Ikko Eltociear Ashimine 870b0db23d Fix typo in module.cpp (#686)
comparision -> comparison
2023-07-25 21:33:30 -07:00
Philipp Trulson a4c19e3430 Enable KeyDB FLASH in Docker images (#687) 2023-07-25 21:33:30 -07:00
Pim van den Broek 9a8a36e8ee Fix redis-cli symlink (#689) 2023-07-25 21:33:30 -07:00
John Sully aef01878a5 Build break fix 2023-07-25 21:32:52 -07:00
John Sully bd6eb379d5 Prevent crash on free when using repl-disk-buffer-reserve (#207) 2023-07-25 21:32:52 -07:00
John Sully 06a49a6179 Fix OOM issues during full sync when fork bgsave is used (#209) 2023-07-25 21:32:52 -07:00
John Sully 9ab1278cf1 Prevent a deadlock when running CLIENT KILL with large numbers of clients (#206) 2023-07-25 21:32:52 -07:00
Alex Cope cc8ed88577 nullptr 2023-07-25 21:32:52 -07:00
Alex Cope 0d2f98d6e1 fix compile error 2023-07-25 21:32:52 -07:00
Alex Cope 872c60566f use enum class 2023-07-25 21:32:52 -07:00
Alex Cope b5a20a4f91 modify 2023-07-25 21:32:52 -07:00
Alex Cope cf4735b61e ok 2023-07-25 21:32:52 -07:00
Alex Cope b2ecf2c856 fix 2023-07-25 21:32:52 -07:00
Alex Cope e80aba694c fix 2023-07-25 21:32:52 -07:00
Alex Cope f774a17d8a fix 2023-07-25 21:32:52 -07:00
Alex Cope 329bf17e97 fix 2023-07-25 21:32:52 -07:00
Alex Cope 4da21dd328 clean 2023-07-25 21:32:52 -07:00
Alex Cope 3792ffb580 fix 2023-07-25 21:32:52 -07:00
Alex Cope 271c2cb92c meminfo 2023-07-25 21:32:52 -07:00
Alex Cope 4c0cf9e5cd fix 2023-07-25 21:32:52 -07:00
Alex Cope e3cdc765a1 fix 2023-07-25 21:32:52 -07:00
Alex Cope 60d74262dc fixes 2023-07-25 21:32:52 -07:00
Alex Cope 65b7fafd29 llu 2023-07-25 21:32:52 -07:00
Alex Cope ac7e6c0691 fix compiler errors 2023-07-25 21:32:52 -07:00
Alex Cope 87012bf98e update sys free 2023-07-25 21:32:52 -07:00
Alex Cope 1dc5ea40f3 update 2023-07-25 21:32:51 -07:00
Alex Cope 22d5ce41f3 update 2023-07-25 21:32:51 -07:00
Alex Cope c9d63c50c0 update 2023-07-25 21:32:51 -07:00
Alex Cope 39e3141a0a revert 2023-07-25 21:32:51 -07:00
Alex Cope 1f2c179124 with config 2023-07-25 21:32:24 -07:00
Alex Cope 4c3d9341fd port changes to 6.3 2023-07-25 21:31:42 -07:00
Rolf Ahrenberg 821b39acac Use tini in Alpine-based Docker image (#682) 2023-07-26 00:20:41 -04:00
John Sully c2c831dc53 Build break fix 2023-07-14 19:49:46 +00:00
John Sully bef77862e7 Prevent crash on free when using repl-disk-buffer-reserve (#207) 2023-07-14 14:39:23 -04:00
John Sully d552fd01e1 Fix OOM issues during full sync when fork bgsave is used (#209) 2023-07-14 14:39:09 -04:00
John Sully c0177198a8 Prevent a deadlock when running CLIENT KILL with large numbers of clients (#206) 2023-07-14 14:38:39 -04:00
Karthick Ariyaratnam (A) 0d01b810c3 Fix a fug where repl-backlog-size config was modifed in keydb.conf with the runtime value during config rewrite. 2023-07-14 13:12:16 -04:00
Karthick AriyaratnamandKarthick Ariyaratnam 501ae473e1 Fix a bug where a temp rdb file with zero bytes is generated in flash mode. (#696)
Co-authored-by: Karthick Ariyaratnam (A) <k00809413@china.huawei.com>
2023-07-13 13:26:00 -04:00
Alex Cope 72b47eddc8 Merge pull request #204 from Snapchat/acope-evict-main
Force evictions when system available memory is low
2023-07-10 06:38:23 -07:00
Alex Cope 970e594dca nullptr 2023-07-07 11:55:49 -07:00
Alex Cope c0c9230c73 fix compile error 2023-07-07 11:37:09 -07:00
Alex Cope e35ce24985 use enum class 2023-07-07 11:07:33 -07:00
Alex Cope 1c8f1a0931 modify 2023-07-07 07:37:07 -07:00
Alex Cope e6bc1c5585 ok 2023-07-06 15:22:52 -07:00
Ikko Eltociear Ashimine 6f77a82480 Fix typo in module.cpp (#686)
comparision -> comparison
2023-07-05 18:38:39 -04:00
Philipp Trulson 8e052f728c Enable KeyDB FLASH in Docker images (#687) 2023-07-05 18:38:14 -04:00
Pim van den Broek 26285d3dd5 Fix redis-cli symlink (#689) 2023-07-05 18:37:35 -04:00
Alex Cope 184185a633 fix 2023-06-30 11:23:43 -07:00
Alex Cope e80ff9ec70 fix 2023-06-30 11:23:00 -07:00
Alex Cope 01b7bc4cc1 fix 2023-06-30 11:21:55 -07:00
Alex Cope 5237d29dc1 fix 2023-06-30 11:21:18 -07:00
Alex Cope 3e528f2619 clean 2023-06-30 11:20:29 -07:00
Alex Cope 1d53d8b75a fix 2023-06-30 11:18:34 -07:00
Alex Cope 4b72775a5c meminfo 2023-06-30 11:05:12 -07:00
Alex Cope b3492b5084 fix 2023-06-30 10:45:19 -07:00
Alex Cope 3e67b43a3b fix 2023-06-30 09:53:44 -07:00
Alex Cope f9978c5961 fixes 2023-06-30 09:49:59 -07:00
Alex Cope d346601067 llu 2023-06-30 09:39:49 -07:00
Alex Cope c894255fea fix compiler errors 2023-06-29 14:58:08 -07:00
Alex Cope 88b24eb68a update sys free 2023-06-29 14:21:00 -07:00
Alex Cope 5b98a58c0e update 2023-06-29 13:41:34 -07:00
Alex Cope 4a9a9f9292 update 2023-06-29 13:23:16 -07:00
Alex Cope 147baeeed3 update 2023-06-29 13:22:47 -07:00
Alex Cope e029c40fdb revert 2023-06-28 23:19:34 -07:00
Alex Cope 4ab128b86a fix conflict 2023-06-28 17:37:33 -07:00
Alex Cope 4e22068f2f with config 2023-06-28 17:31:40 -07:00
Alex Cope 6efe344587 port changes to 6.3 2023-06-27 14:18:14 -07:00
c17b9f47ac Cherry picking keydb changes from keydbpro to main (#203)
* Audit Logging for KeyProxy and KeyDB (#144)

* Audit Log: log cert fingerprint (#151)

* Add more flash storage stats to info command.

* Remove unneeded libs when not building FLASH

* Fix mem leak

* Allow the reservation of localhost connections to ensure health checks always succeed even at maxclients (#181)

* Enable a force option for commands (#183)

* Fix missing newline and excessive logging in the CLI

* Support NO ONE for "CLUSTER REPLICATE" command.

Co-authored-by: Jacob Bohac <jbohac@snapchat.com>
Co-authored-by: Sergey Kolosov <skolosov@snapchat.com>
Co-authored-by: John Sully <jsully@snapchat.com>
Co-authored-by: John Sully <john@csquare.ca>
2023-06-27 16:23:20 -04:00
John Sully f53e0337ef Port load shedding and availability-zone (#202) 2023-06-27 15:37:28 -04:00
Malavan Sotheeswaran dcd623b079 Merge remote-tracking branch 'internal/main' 2023-06-21 16:31:55 -07:00
Karthick Ariyaratnamandk00809413 533c1f70b5 Fix a bug where swapdb result was not recovered after keydb restarts in FLASH mode. (#668)
Co-authored-by: k00809413 <karthick.ariyaratnam1@huawei.com>
2023-06-21 16:29:31 -07:00
Malavan Sotheeswaran a77548e44a Make inserts in flight a shared_ptr to avoid double free (#198)
* remove keyproxy test from machamp

* Update build.yaml

* make insertsinflight shared
2023-06-21 16:28:57 -07:00
Malavan Sotheeswaran dde2c27a7e have FreeMemoryLazyFree increment dict refcount so it doesn't try to use a dict that has already been released (#199) 2023-06-21 16:28:57 -07:00
Malavan Sotheeswaran b258322f9e Fix propagate in transaction crash (#200)
* ignore propagate_in_transaction in processCommand if command is async

* make propogate_in_transaction thread local

* forgot module.cpp

* move client_pause_in_transaction to thread local

* don't do async commands while in eval or exec

* don't do keys or scan async if in transaction
2023-06-21 16:28:57 -07:00
Malavan Sotheeswaran 9e310b1bea Make inserts in flight a shared_ptr to avoid double free (#198)
* remove keyproxy test from machamp

* Update build.yaml

* make insertsinflight shared
2023-06-21 16:47:21 -04:00
Malavan Sotheeswaran 2adf93ba0e have FreeMemoryLazyFree increment dict refcount so it doesn't try to use a dict that has already been released (#199) 2023-06-21 16:47:12 -04:00
Malavan Sotheeswaran 8242ba5b47 Fix propagate in transaction crash (#200)
* ignore propagate_in_transaction in processCommand if command is async

* make propogate_in_transaction thread local

* forgot module.cpp

* move client_pause_in_transaction to thread local

* don't do async commands while in eval or exec

* don't do keys or scan async if in transaction
2023-06-21 16:40:35 -04:00
Karthick Ariyaratnamandk00809413 46660b9f18 Fix a bug where swapdb result was not recovered after keydb restarts in FLASH mode. (#668)
Co-authored-by: k00809413 <karthick.ariyaratnam1@huawei.com>
2023-06-21 16:26:14 -04:00
Malavan Sotheeswaran 45b41fbde6 Merge remote-tracking branch 'origin/main' 2023-06-15 15:06:45 -07:00
Malavan Sotheeswaran 62f3c37b87 Throw load error when replicaof config is before active-replica or multi-master configs (#666)
* load error when replicaof is set before multi-master or active-replica

* only an issue if enabled
2023-06-12 15:06:59 -07:00
Malavan Sotheeswaran 05430586d1 Throw load error when replicaof config is before active-replica or multi-master configs (#666)
* load error when replicaof is set before multi-master or active-replica

* only an issue if enabled
2023-06-01 13:38:34 -04:00
Malavan Sotheeswaran 163f903f3f remove keyproxy test from machamp
* Update build.yaml
2023-05-30 14:49:06 -04:00
Malavan Sotheeswaran dc419a4e6e keys after brpop causes crash due to timeout not being reset (#662) 2023-05-30 01:01:51 -04:00
Malavan Sotheeswaran 3336c4f43e Flash slot to key (#653)
* add hash prefixing to rocksdb

* don't need to offset internal keys

* remove hashslot prefix when loading from rocksdb

* don't prefix internal keys

* implement slot to keys with rocksdb

* fix compile

* add hashslot enum to test storage provider

* add --flash option for tests

* forgot return statement in getKeysInSlot

* check for count mismatch

* forgot ;

* add assert to ensure correctness

* fix warnings

* add option to show logs of 1 server at a time

* add fixed length prefix extractor

* add machamp

* switch machamp to main branch

* add machamp script
2023-05-30 01:01:40 -04:00
Alexandre Chichmanian 16e8469316 Fix aws startup check preventing keydb from starting 2023-05-30 00:34:15 -04:00
Malavan Sotheeswaran ef8b1bf373 add fPreSnapshot to evictionTimeProc 2023-05-25 00:03:39 -07:00
Attila Nagy f629a76a2e Make it compile on FreeBSD (#650) 2023-05-08 15:59:36 -04:00
Malavan Sotheeswaran e4dacb467f add config to disable storage key cache 2023-04-25 11:15:57 -07:00
Malavan Sotheeswaran a8c0f9b1c4 limit number of random elements returned based on config, exit loop early if client is being closed 2023-04-18 17:08:27 -07:00
Malavan Sotheeswaran 36d515ef39 ptrFromObj to szFromObj 2023-04-18 17:08:27 -07:00
Malavan Sotheeswaran 525c97d6cd ->ptr to ptrFromObj 2023-04-18 17:08:27 -07:00
4c200e0985 fix hincrbyfloat not to create a key if the new value is invalid (#11149)
Check the validity of the value before performing the create operation,
prevents new data from being generated even if the request fails to execute.

Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: chendianqiang <chendianqiang@meituan.com>
Co-authored-by: Binbin <binloveplay1314@qq.com>
2023-04-18 17:08:27 -07:00
Oran Agra d4c0a970e7 Integer Overflow in RAND commands can lead to assertion (CVE-2023-25155) (#11857)
Issue happens when passing a negative long value that greater than
the max positive value that the long can store.
2023-04-18 17:08:27 -07:00
Malavan Sotheeswaran 1735e73a35 don't count base repl buffer size as overhead (#629) 2023-04-12 16:01:22 -04:00
Heng Kuang f9ccf90566 fix the swapdb issue (#620) 2023-04-12 15:08:09 -04:00
Malavan Sotheeswaran f54e3ea367 restore fork impl of save to replica sockets (#622)
* restore fork impl of save to slave sockets

* default to fork bgsave
2023-04-06 22:13:12 -04:00
Ubuntu 77bc0afad4 add key eviction number for storage 2023-04-06 15:09:10 -04:00
Malavan Sotheeswaran 858db2645d add g_expirelock to previously async accesses to expireset (#618)
* add expire_lock to previously async access to expireset

* add flash option to create-cluster
2023-03-30 23:23:56 -04:00
Malavan Sotheeswaran 06380e5fe1 Add KeyDB global variable to lua api (#606) 2023-03-14 13:45:29 -07:00
Malavan Sotheeswaran deaaa992bd don't do if statement on every key for flash load, use stack object for module notify (#604) 2023-03-13 18:16:17 -04:00
Malavan Sotheeswaran 715f832b00 make connectWithMaster error message less confusing (#592) 2023-03-08 16:00:53 -05:00
Malavan Sotheeswaran c8dd6744da Add config for AWS s3 rdb save/load. (#591)
* add s3 config

* missing include

* ignore s3 config in config test

* Update introspection.tcl

* Update config.cpp
2023-03-08 16:00:44 -05:00
Malavan Sotheeswaran b398021b2c update slotToKey during fast load (#589) 2023-03-08 15:54:50 -05:00
Malavan Sotheeswaran c2077a2fbd Fix rdb fd leak (#579)
* close tempfile when done bgsave

* declare getTempFileName

* open close child pipes in correct place

* assert no existing child pipe when opening new child pipe
2023-03-02 13:51:47 -05:00
Malavan Sotheeswaran a9995d2e54 Upgrade rocksdb to v7.9.2 (#555)
* upgrade to rocksdb v7.9.2

* need c++17 for rocksdb

* don't prefix jemalloc

* different exception specifier
2023-03-02 13:51:21 -05:00
Malavan Sotheeswaran 81c6c7902d Fix possible crash in prefetchKeysAsync with flash enabled. (#578)
* remove short circuit as it is unsafe
2023-02-22 19:27:24 -05:00
Malavan Sotheeswaran 4ac449aee7 Merge pull request #575 from EreMaijala/main-issue-537
Close child info pipes when RDB save thread is done.
2023-02-21 17:47:43 -05:00
Ere Maijala db2c3cd7e8 Close child info pipes when RDB save thread is done. 2023-02-20 11:07:21 +02:00
Malavan Sotheeswaran 5f5eb0e1b0 Merge pull request #570 from Snapchat/fix_snprint_uses
Fix snprintf uses to avoid hardcoded sizes
2023-02-14 22:20:29 -05:00
Malavan Sotheeswaran 045cc52c1f don't use sizeof on malloc'd pointer 2023-02-14 18:23:35 -08:00
Malavan Sotheeswaran 819bcc1f2a don't sizeof on a pointer 2023-02-14 17:51:26 -08:00
Malavan Sotheeswaran 7fcbfac103 snprintf fix 2023-02-14 17:51:19 -08:00
Malavan Sotheeswaran 0bf6a0c375 Merge pull request #560 from Snapchat/aof_fixes
* technically possible for child_type == CHILD_TYPE_AOF without active child

* don't release lock on child as it can hang

* need child specific release that doesn't trigger cv

* refactor aeReleaseForkLockChild to capture releaseRead case

* rdb_child_pid isn't the correct value
2023-02-13 13:07:53 -05:00
Malavan Sotheeswaran 62d281b522 Don't do async keys command when client cannot block (#564)
Fixes #562
2023-02-13 13:01:41 -05:00
Malavan Sotheeswaran 9f4c6e6f15 using forked bg save causes more harm than good 2023-02-10 01:23:52 -08:00
Malavan Sotheeswaran 6c26f91bcd rdb_child_pid isn't the correct value 2023-02-10 01:13:12 -08:00
Malavan Sotheeswaran 932fc207ea make bgsave fork by default 2023-02-09 12:22:29 -08:00
Malavan Sotheeswaran 0cba0ed30b refactor aeReleaseForkLockChild to capture releaseRead case 2023-02-09 12:06:27 -08:00
Malavan Sotheeswaran b5ab1d64e2 need child specific release that doesn't trigger cv 2023-02-09 10:28:22 -08:00
Malavan Sotheeswaran 3492615c6a don't release lock on child as it can hang 2023-02-09 09:39:25 -08:00
Malavan Sotheeswaran f3a9d2e0cd technically possible for child_type == CHILD_TYPE_AOF without active child 2023-02-06 20:33:21 -08:00
Malavan Sotheeswaran a1978ce04c Add support to notify modules of keys loaded by flash on startup (#536) 2023-02-06 12:52:32 -05:00
Malavan Sotheeswaran 5123e2b3a1 change hasActiveChildProcess to return true only when there is an actual child process (#558)
change hasActiveChildProcess to return true only when there is an actual child process, add hasActiveChildProcessOrBGSave to catch case of forkless bgsave
2023-02-03 13:36:06 -05:00
John Sully 687850a612 Make semi-ordered-set rehashing configurable to aid in latency tuning 2023-01-31 11:30:39 -08:00
John Sully d4555a6e38 We need to check for AE_DONT_WAIT after it is set in beforeSleep otherwise we'll miss it 2023-01-31 11:27:59 -08:00
Malavan Sotheeswaran fc8e4804b2 remove extra call to handleBlockedClientsTimeout (#557) 2023-01-30 16:23:00 -05:00
benschermel 1113488991 fix typo & discrepancy 2023-01-25 11:42:36 -05:00
Malavan Sotheeswaran 694fc59f2a move memory check before rocksdb to avoid crash in child 2023-01-23 13:26:27 -08:00
Malavan Sotheeswaran 7e91711086 fix debian 12 test failure 2023-01-23 13:26:24 -08:00
Malavan Sotheeswaran ff7991951f Fix optional flash build (#549)
Currently flash is always built, with this change it is actually optional.
2023-01-23 16:22:01 -05:00
Malavan Sotheeswaran d7c34a8ced Properly handle case where we delete a key that doesn't exist while a write batch is active (#550) 2023-01-23 16:21:27 -05:00
0xflotus c3343ddbd7 fix: small errors in readme (#535) 2023-01-20 16:11:01 -05:00
Malavan SotheeswaranandOran Agra ad0be5666c Security fixes from redis: Avoid integer overflows in SETRANGE and SORT, Fix range issues in ZRANDMEMBER and HRANDFIELD (#547)
* Avoid integer overflows in SETRANGE and SORT (CVE-2022-35977) (#11720)

Authenticated users issuing specially crafted SETRANGE and SORT(_RO)
commands can trigger an integer overflow, resulting with Redis attempting
to allocate impossible amounts of memory and abort with an OOM panic.

* Fix range issues in ZRANDMEMBER and HRANDFIELD (CVE-2023-22458) (#11674)

missing range check in ZRANDMEMBER and HRANDIFLD leading to panic due
to protocol limitations

* use std::min/max

* add assert_not_equal

Co-authored-by: Oran Agra <oran@redislabs.com>
2023-01-17 17:10:39 -05:00
131 changed files with 7065 additions and 1384 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+5 -7
View File
@@ -4,7 +4,7 @@
##### KeyDB is now a part of Snap Inc! Check out the announcement [here](https://docs.keydb.dev/news/2022/05/12/keydb-joins-snap) ##### KeyDB is now a part of Snap Inc! Check out the announcement [here](https://docs.keydb.dev/news/2022/05/12/keydb-joins-snap)
##### [Release v6.3.0](https://github.com/EQ-Alpha/KeyDB/releases/tag/v6.3.0) is here with major improvements as we consolodate our Open Source and Enterprise offerings into a single BSD-3 licensed project. See our [roadmap](https://docs.keydb.dev/docs/coming-soon) for details. ##### [Release v6.3.0](https://github.com/EQ-Alpha/KeyDB/releases/tag/v6.3.0) is here with major improvements as we consolidate our Open Source and Enterprise offerings into a single BSD-3 licensed project. See our [roadmap](https://docs.keydb.dev/docs/coming-soon) for details.
##### Want to extend KeyDB with Javascript? Try [ModJS](https://github.com/JohnSully/ModJS) ##### Want to extend KeyDB with Javascript? Try [ModJS](https://github.com/JohnSully/ModJS)
@@ -37,7 +37,7 @@ Because of this difference of opinion features which are right for KeyDB may not
Project Support Project Support
------------------- -------------------
The KeyDB team maintains this project as part of Snap Inc. KeyDB is used by Snap as part of its caching infrastructure and is fully open sourced. There is no separate commercial product and no paid support options available. We really value collaborating with the open source community and welcome PRs, bug reports, and open discussion. For community support or to get involved further with the project check out our community support options [here](https://docs.keydb.dev/docs/support) (slack, forum, meetup, github issues). Our team monitors these channlels regularly. The KeyDB team maintains this project as part of Snap Inc. KeyDB is used by Snap as part of its caching infrastructure and is fully open sourced. There is no separate commercial product and no paid support options available. We really value collaborating with the open source community and welcome PRs, bug reports, and open discussion. For community support or to get involved further with the project check out our community support options [here](https://docs.keydb.dev/docs/support) (slack, forum, meetup, github issues). Our team monitors these channels regularly.
Additional Resources Additional Resources
@@ -49,8 +49,6 @@ Join us on [Slack](https://docs.keydb.dev/slack/)
Learn more using KeyDB's extensive [documentation](https://docs.keydb.dev) Learn more using KeyDB's extensive [documentation](https://docs.keydb.dev)
Post to our [Community Forum](https://community.keydb.dev)
See the [KeyDB Roadmap](https://docs.keydb.dev/docs/coming-soon) to see what's in store See the [KeyDB Roadmap](https://docs.keydb.dev/docs/coming-soon) to see what's in store
@@ -80,7 +78,7 @@ The minimum number of clients on a thread before KeyDB assigns new connections t
``` ```
replica-weighting-factor 2 replica-weighting-factor 2
``` ```
KeyDB will attempt to balance clients across threads evenly; However, replica clients are usually much more expensive than a normal client, and so KeyDB will try to assign fewer clients to threads with a replica. The weighting factor below is intented to help tune this behavior. A replica weighting factor of 2 means we treat a replica as the equivalent of two normal clients. Adjusting this value may improve performance when replication is used. The best weighting is workload specific - e.g. read heavy workloads should set this to 1. Very write heavy workloads may benefit from higher numbers. KeyDB will attempt to balance clients across threads evenly; However, replica clients are usually much more expensive than a normal client, and so KeyDB will try to assign fewer clients to threads with a replica. The weighting factor below is intended to help tune this behavior. A replica weighting factor of 2 means we treat a replica as the equivalent of two normal clients. Adjusting this value may improve performance when replication is used. The best weighting is workload specific - e.g. read heavy workloads should set this to 1. Very write heavy workloads may benefit from higher numbers.
``` ```
active-client-balancing yes active-client-balancing yes
@@ -141,9 +139,9 @@ To append a suffix to KeyDB program names, use:
***Note that the following dependencies may be needed: ***Note that the following dependencies may be needed:
% sudo apt-get install autoconf autotools-dev libnuma-dev libtool % sudo apt-get install autoconf autotools-dev libnuma-dev libtool
To buik=ld with TLS support, use: KeyDB by default is built with TLS enabled. To build without TLS support, use:
% make BUILD_TLS=yes % make BUILD_TLS=no
Running the tests with TLS enabled (you will need `tcl-tls` Running the tests with TLS enabled (you will need `tcl-tls`
installed): installed):
+70
View File
@@ -0,0 +1,70 @@
# Doc: https://wiki.sc-corp.net/pages/viewpage.action?pageId=121500284
version: 1
machamp:
keydb-build:
# Optional - build counter is linked to the build def
tag_template: "0.0.%build.counter%-%sha%"
# Optional - value in seconds before a build is terminated, default is 3600 seconds
timeout: 3600
# Optional - update ghe or not, default to true
update_ghe: true
code_coverage: false
# Required
steps:
make-build:
type: cmd
# https://github.sc-corp.net/Snapchat/img/tree/master/keydb/ubuntu-20-04
builder_image: us.gcr.io/snapchat-build-artifacts/prod/snapchat/img/keydb/keydb-ubuntu-20-04@sha256:cf869a3f5d1de1e1d976bb906689c37b7031938eb68661b844a38c532f27248c
command: ./machamp_scripts/build.sh
tls-test:
type: cmd
parent: make-build
# https://github.sc-corp.net/Snapchat/img/tree/master/keydb/ubuntu-20-04
builder_image: us.gcr.io/snapchat-build-artifacts/prod/snapchat/img/keydb/keydb-ubuntu-20-04@sha256:cf869a3f5d1de1e1d976bb906689c37b7031938eb68661b844a38c532f27248c
command: ./runtest --clients 4 --verbose --tls
cluster-test:
type: cmd
parent: make-build
# https://github.sc-corp.net/Snapchat/img/tree/master/keydb/ubuntu-20-04
builder_image: us.gcr.io/snapchat-build-artifacts/prod/snapchat/img/keydb/keydb-ubuntu-20-04@sha256:cf869a3f5d1de1e1d976bb906689c37b7031938eb68661b844a38c532f27248c
command: ./runtest-cluster --tls
sentinel-test:
type: cmd
parent: make-build
# https://github.sc-corp.net/Snapchat/img/tree/master/keydb/ubuntu-20-04
builder_image: us.gcr.io/snapchat-build-artifacts/prod/snapchat/img/keydb/keydb-ubuntu-20-04@sha256:cf869a3f5d1de1e1d976bb906689c37b7031938eb68661b844a38c532f27248c
command: ./runtest-sentinel
module-test:
type: cmd
parent: make-build
# https://github.sc-corp.net/Snapchat/img/tree/master/keydb/ubuntu-20-04
builder_image: us.gcr.io/snapchat-build-artifacts/prod/snapchat/img/keydb/keydb-ubuntu-20-04@sha256:cf869a3f5d1de1e1d976bb906689c37b7031938eb68661b844a38c532f27248c
command: ./runtest-moduleapi
rotation-test:
type: cmd
parent: make-build
# https://github.sc-corp.net/Snapchat/img/tree/master/keydb/ubuntu-20-04
builder_image: us.gcr.io/snapchat-build-artifacts/prod/snapchat/img/keydb/keydb-ubuntu-20-04@sha256:cf869a3f5d1de1e1d976bb906689c37b7031938eb68661b844a38c532f27248c
command: ./runtest-rotation
keydb-docker-build:
# Optional - build counter is linked to the build def
tag_template: "%sha%"
# Optional - value in seconds before a build is terminated, default is 3600 seconds
timeout: 3600
# Optional - update ghe or not, default to true
update_ghe: true
code_coverage: false
# Required
steps:
# to ensure a clearer docker build env
code-checkout:
type: cmd
command: echo checkout
# default machamp builder image does not work for multi arch
builder_image: us.gcr.io/snapchat-build-artifacts/prod/snapchat/img/ubuntu/ubuntu-23-04@sha256:bd43177a80e6ce1c3583e8ea959b88a9081c0f56b765ec9c5a157c27a637c23b
docker:
parent: code-checkout
type: docker # published images can be found in https://console.cloud.google.com/gcr/images/machamp-prod/global/keydb
dockerfile: machamp_scripts/Dockerfile
image_name: keydb # git commit sha will be deafult tag in the final image
workspace_context: ./ # This is the workspace context that your Dockerfile will use to move files around. <Root of checkout repository>/<Workspace Context>/<Dockerfile> If the workspace context is just the root of the repository, you can just use "./".
+35
View File
@@ -0,0 +1,35 @@
# Doc: https://wiki.sc-corp.net/display/TOOL/ci.yaml+User+Guide
version: 1
on:
# https://wiki.sc-corp.net/display/TOOL/Onboard+Machamp+Build+By+ci.yaml+Configuration
# on pull_request is used for any pr build
pull_request:
- branches: ['!!main', '*'] # this branch pattern means any branch but not main branch will trigger this pr build
workflows:
# All builds that use machamp should use the defined `backend_workflow`
- workflow_type: backend_workflow
# references a build defined in build.yaml
build_name: keydb-build
arch_types: ["amd64", "arm64"]
- workflow_type: backend_workflow
# references a build defined in build.yaml
build_name: keydb-docker-build
arch_types: ["amd64", "arm64"]
# on push is used for release branch, meaning: trigger this build when there is commit pushed to this branch
push:
- branches: [main]
workflows:
- workflow_type: backend_workflow
build_name: keydb-build
arch_types: ["amd64", "arm64"]
- workflow_type: backend_workflow
# references a build defined in build.yaml
build_name: keydb-docker-build
arch_types: ["amd64", "arm64"]
# below defines which branch is release branch / release tag
machamp:
releases:
# Note: machamp will only respect the ci.yaml file from default branch for "release branch" definition (most repositories using master/main as default branch)
# https://wiki.sc-corp.net/display/TOOL/Onboard+Machamp+Build+By+ci.yaml+Configuration
- branch_name: ^main$
+7 -1
View File
@@ -34,11 +34,17 @@ ifneq ($(shell sh -c '[ -f .make-ldflags ] && cat .make-ldflags || echo none'),
endif endif
distclean: distclean:
ifneq ($(USE_SYSTEM_HIREDIS),yes)
-(cd hiredis && $(MAKE) clean) > /dev/null || true -(cd hiredis && $(MAKE) clean) > /dev/null || true
endif
-(cd linenoise && $(MAKE) clean) > /dev/null || true -(cd linenoise && $(MAKE) clean) > /dev/null || true
-(cd lua && $(MAKE) clean) > /dev/null || true -(cd lua && $(MAKE) clean) > /dev/null || true
ifneq ($(USE_SYSTEM_JEMALLOC),yes)
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true -(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
endif
ifneq ($(USE_SYSTEM_ROCKSDB),yes)
-(cd rocksdb && $(MAKE) clean) > /dev/null || true -(cd rocksdb && $(MAKE) clean) > /dev/null || true
endif
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true -(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
-(rm -f .make-*) -(rm -f .make-*)
@@ -95,7 +101,7 @@ JEMALLOC_LDFLAGS= $(LDFLAGS)
jemalloc: .make-prerequisites jemalloc: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd jemalloc && ./configure --with-version=5.2.1-0-g0 --with-lg-quantum=3 --with-jemalloc-prefix=je_ --disable-cxx CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" cd jemalloc && ./configure --with-version=5.2.1-0-g0 --with-lg-quantum=3 --disable-cxx CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)"
cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a
.PHONY: jemalloc .PHONY: jemalloc
+105
View File
@@ -0,0 +1,105 @@
AccessModifierOffset: -4
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: Empty
BinPackArguments: false
BinPackParameters: false
ColumnLimit: 120
IndentCaseLabels: false
IndentWidth: 4
---
Language: Cpp
# BasedOnStyle: Google
#AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: true
AlignOperands: true
AlignTrailingComments: true
#AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
#AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
#BinPackArguments: true
#BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
#ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IncludeCategories:
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
#IndentCaseLabels: true
#IndentWidth: 2
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 8
UseTab: Never
...
+30
View File
@@ -0,0 +1,30 @@
name: Coverage
on: [push, pull_request]
jobs:
coverage:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y -qq make cmake gcc g++ lcov bc
- name: build
shell: bash
run: |
export LD_LIBRARY_PATH=.:$(cat /etc/ld.so.conf.d/* | grep -vF "#" | tr "\\n" ":" | sed -e "s/:$//g")
cmake . -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=On
make all -j$(nproc)
- name: coverage
shell: bash
run: |
make coverage
lines=$(lcov --summary coverage.info | grep -F lines | awk '{print $2}' | sed -e "s/%//g")
if (( $(echo "${lines} < ${COVERAGE_THRESHOLD}" | bc -l) )); then
echo "Line coverage dropped below ${COVERAGE_THRESHOLD}% to ${lines}%"
exit 1
fi
env:
COVERAGE_THRESHOLD: 85.0
+13
View File
@@ -0,0 +1,13 @@
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: lint
uses: DoozyX/clang-format-lint-action@v0.12
with:
clangFormatVersion: 12
source: './include/cpp-statsd-client ./tests'
+23
View File
@@ -0,0 +1,23 @@
name: Linux
on: [push, pull_request]
jobs:
linux:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y -qq make cmake gcc g++
- name: build
shell: bash
run: |
export LD_LIBRARY_PATH=.:$(cat /etc/ld.so.conf.d/* | grep -vF "#" | tr "\\n" ":" | sed -e "s/:$//g")
cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SANITIZERS=On
make all -j$(nproc)
- name: test
shell: bash
run: |
make test
+18
View File
@@ -0,0 +1,18 @@
name: Windows
on: [push, pull_request]
jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: dependencies
run: |
choco install cmake
- name: build
run: |
cmake -S . -B build -G "Visual Studio 16 2019" -A x64
cmake --build build --target ALL_BUILD --config Release
- name: test
run: |
cmake --build build --target RUN_TESTS --config Release
+1
View File
@@ -0,0 +1 @@
bin
+85
View File
@@ -0,0 +1,85 @@
# Basic project setup
cmake_minimum_required(VERSION 3.5)
project(cpp-statsd-client
VERSION 1.0.2
LANGUAGES CXX
DESCRIPTION "A header-only StatsD client implemented in C++"
HOMEPAGE_URL "https://github.com/vthiery/cpp-statsd-client")
option(CPP_STATSD_STANDALONE "Allows configuration of targets for verifying library functionality" ON)
option(ENABLE_TESTS "Build tests" ON)
option(ENABLE_COVERAGE "Build with coverage instrumentalisation" OFF)
if(NOT CPP_STATSD_STANDALONE)
set(ENABLE_TESTS OFF)
set(ENABLE_COVERAGE OFF)
endif()
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
find_package(Threads)
# Optional code coverage targets
if(ENABLE_COVERAGE)
set(COVERAGE_EXCLUDES /usr/*)
include(${PROJECT_SOURCE_DIR}/cmake/CodeCoverage.cmake)
APPEND_COVERAGE_COMPILER_FLAGS()
SETUP_TARGET_FOR_COVERAGE_LCOV(NAME coverage
EXECUTABLE testStatsdClient
DEPENDENCIES ${PROJECT_NAME}
)
endif()
# The library target
add_library(${PROJECT_NAME} INTERFACE)
target_include_directories(
${PROJECT_NAME}
INTERFACE $<BUILD_INTERFACE:${${PROJECT_NAME}_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_link_libraries(${PROJECT_NAME} INTERFACE Threads::Threads)
if(WIN32)
target_link_libraries(${PROJECT_NAME} INTERFACE ws2_32)
endif()
# The installation and pkg-config-like cmake config
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}_Targets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
configure_package_config_file(
"${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in"
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION
${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
install(EXPORT ${PROJECT_NAME}_Targets
FILE ${PROJECT_NAME}Targets.cmake
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include DESTINATION include)
if(ENABLE_TESTS)
# The test targets
add_executable(testStatsdClient ${CMAKE_CURRENT_SOURCE_DIR}/tests/testStatsdClient.cpp)
if(WIN32)
target_compile_options(testStatsdClient PRIVATE -W4 -WX /external:W0)
else()
target_compile_options(testStatsdClient PRIVATE -Wall -Wextra -pedantic -Werror)
endif()
target_include_directories(testStatsdClient PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests)
target_link_libraries(testStatsdClient ${PROJECT_NAME})
set_property(TARGET testStatsdClient PROPERTY CXX_STANDARD 11)
set_property(TARGET testStatsdClient PROPERTY CXX_EXTENSIONS OFF)
# The test suite
enable_testing()
add_test(ctestTestStatsdClient testStatsdClient)
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS testStatsdClient)
endif()
+9
View File
@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2017 Vincent Thiery
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+22
View File
@@ -0,0 +1,22 @@
# simple makefile to build, test and clean
BUILD_MODE ?= Release
ENABLE_COVERAGE ?= On
build: clean
@echo "Build in ${BUILD_MODE} mode"
mkdir -p bin/${BUILD_MODE}
@cd bin/${BUILD_MODE}; cmake ../../ -DCMAKE_BUILD_TYPE=${BUILD_MODE} -DENABLE_COVERAGE=${ENABLE_COVERAGE}
@cd bin/${BUILD_MODE}; make
test: build
@cd bin/${BUILD_MODE}; make test
coverage: build
@cd bin/${BUILD_MODE}; make coverage
install: build
@cd bin/${BUILD_MODE}; make install
clean:
@rm -rf bin
+150
View File
@@ -0,0 +1,150 @@
# C++ StatsD Client
![logo](https://raw.githubusercontent.com/vthiery/cpp-statsd-client/master/images/logo.svg?sanitize=true)
[![Release](https://img.shields.io/github/release/vthiery/cpp-statsd-client.svg?style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/releases/latest)
![License](https://img.shields.io/github/license/vthiery/cpp-statsd-client?style=for-the-badge)
[![Linux status](https://img.shields.io/github/workflow/status/vthiery/cpp-statsd-client/Linux?label=Linux&style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/actions/workflows/linux.yml?query=branch%3Amaster++)
[![Windows status](https://img.shields.io/github/workflow/status/vthiery/cpp-statsd-client/Windows?label=Windows&style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/actions/workflows/windows.yml?query=branch%3Amaster++)
A header-only StatsD client implemented in C++.
The client allows:
- batching,
- change of configuration at runtime,
- user-defined frequency rate.
## Install and Test
### Makefile
In order to install the header files and/or run the tests, simply use the Makefile and execute
```sh
make install
```
and
```sh
make test
```
### Conan
If you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add statsdclient/x.y.z@vthiery/stable to your conanfile.py's requires, where x.y.z is the release version you want to use. Please file issues here if you experience problems with the packages. You can also directly download the latest version [here](https://bintray.com/vthiery/conan-packages/statsdclient%3Avthiery/_latestVersion).
## Usage
### Example
A simple example of how to use the client:
```cpp
#include "StatsdClient.hpp"
using namespace Statsd;
int main() {
// Define the client on localhost, with port 8080,
// using a prefix,
// a batching size of 20 bytes,
// and three points of precision for floating point gauge values
StatsdClient client{ "127.0.0.1", 8080, "myPrefix", 20, 3 };
// Increment the metric "coco"
client.increment("coco");
// Decrement the metric "kiki"
client.decrement("kiki");
// Adjusts "toto" by +3
client.count("toto", 2, 0.1f);
// Record a gauge "titi" to 3
client.gauge("titi", 3);
// Record a timing of 2ms for "myTiming"
client.timing("myTiming", 2, 0.1f);
// Send a metric explicitly
client.send("tutu", 4, "c", 2.0f);
exit(0);
}
```
### Advanced Testing
A simple mock StatsD server can be found at `tests/StatsdServer.hpp`. This can be used to do simple validation of your application's metrics, typically in the form of unit tests. In fact this is the primary means by which this library is tested. The mock server itself is not distributed with the library so to use it you'd need to vendor this project into your project. Once you have though, you can test your application's use of the client like so:
```cpp
#include "StatsdClient.hpp"
#include "StatsdServer.hpp"
#include <cassert>
using namespace Statsd;
struct MyApp {
void doWork() const {
m_client.count("bar", 3);
}
private:
StatsdClient m_client{"localhost", 8125, "foo"};
};
int main() {
StatsdServer mockServer;
MyApp app;
app.doWork();
assert(mockServer.receive() == "foo.bar:3|c");
exit(0);
}
```
### Configuration
The configuration of the client must be input when one instantiates it. Nevertheless, the API allows the configuration ot change afterwards. For example, one can do the following:
```cpp
#include "StatsdClient.hpp"
using namespace Statsd;
int main()
{
// Define the client on localhost, with port 8080,
// using a prefix,
// a batching size of 20 bytes,
// and three points of precision for floating point gauge values
StatsdClient client{ "127.0.0.1", 8080, "myPrefix", 20, 3 };
client.increment("coco");
// Set a new configuration, using a different port, a different prefix, and more gauge precision
client.setConfig("127.0.0.1", 8000, "anotherPrefix", 6);
client.decrement("kiki");
}
```
The batchsize is the only parameter that cannot be changed for the time being.
### Batching
The client supports batching of the metrics. The batch size parameter is the number of bytes to allow in each batch (UDP datagram payload) to be sent to the statsd process. This number is not a hard limit. If appending the current stat to the current batch (separated by the `'\n'` character) pushes the current batch over the batch size, that batch is enqueued (not sent) and a new batch is started. If batch size is 0, the default, then each stat is sent individually to the statsd process and no batches are enqueued.
### Sending
As previously mentioned, if batching is disabled (by setting the batch size to 0) then every stat is sent immediately in a blocking fashion. If batching is enabled (ie non-zero) then you may also set the send interval. The send interval controls the time, in milliseconds, to wait before flushing/sending the queued stats batches to the statsd process. When the send interval is non-zero a background thread is spawned which will do the flushing/sending at the configured send interval, in other words asynchronously. The queuing mechanism in this case is *not* lock-free. If batching is enabled but the send interval is set to zero then the queued batchs of stats will not be sent automatically by a background thread but must be sent manually via the `flush` method. The `flush` method is a blocking call.
### Frequency rate
When sending a metric, a frequency rate can be set in order to limit the metrics' sampling. By default, the frequency rate is set to one and won't affect the sampling. If set to a value `epsilon` (0.0001 for the time being) close to one, the sampling is not affected either.
If the frequency rate is set and `epsilon` different from one, the sending will be rejected randomly (the higher the frequency rate, the lower the probability of rejection).
## License
This library is under MIT license.
+708
View File
@@ -0,0 +1,708 @@
# Copyright (c) 2012 - 2017, Lars Bilke
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# CHANGES:
#
# 2012-01-31, Lars Bilke
# - Enable Code Coverage
#
# 2013-09-17, Joakim Söderberg
# - Added support for Clang.
# - Some additional usage instructions.
#
# 2016-02-03, Lars Bilke
# - Refactored functions to use named parameters
#
# 2017-06-02, Lars Bilke
# - Merged with modified version from github.com/ufz/ogs
#
# 2019-05-06, Anatolii Kurotych
# - Remove unnecessary --coverage flag
#
# 2019-12-13, FeRD (Frank Dana)
# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor
# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments.
# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY
# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list
# - Set lcov basedir with -b argument
# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be
# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().)
# - Delete output dir, .info file on 'make clean'
# - Remove Python detection, since version mismatches will break gcovr
# - Minor cleanup (lowercase function names, update examples...)
#
# 2019-12-19, FeRD (Frank Dana)
# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets
#
# 2020-01-19, Bob Apthorpe
# - Added gfortran support
#
# 2020-02-17, FeRD (Frank Dana)
# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters
# in EXCLUDEs, and remove manual escaping from gcovr targets
#
# 2021-01-19, Robin Mueller
# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run
# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional
# flags to the gcovr command
#
# 2020-05-04, Mihchael Davis
# - Add -fprofile-abs-path to make gcno files contain absolute paths
# - Fix BASE_DIRECTORY not working when defined
# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines
#
# 2021-05-10, Martin Stump
# - Check if the generator is multi-config before warning about non-Debug builds
#
# USAGE:
#
# 1. Copy this file into your cmake modules path.
#
# 2. Add the following line to your CMakeLists.txt (best inside an if-condition
# using a CMake option() to enable it just optionally):
# include(CodeCoverage)
#
# 3. Append necessary compiler flags:
# append_coverage_compiler_flags()
#
# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og
#
# 4. If you need to exclude additional directories from the report, specify them
# using full paths in the COVERAGE_EXCLUDES variable before calling
# setup_target_for_coverage_*().
# Example:
# set(COVERAGE_EXCLUDES
# '${PROJECT_SOURCE_DIR}/src/dir1/*'
# '/path/to/my/src/dir2/*')
# Or, use the EXCLUDE argument to setup_target_for_coverage_*().
# Example:
# setup_target_for_coverage_lcov(
# NAME coverage
# EXECUTABLE testrunner
# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*")
#
# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set
# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR)
# Example:
# set(COVERAGE_EXCLUDES "dir1/*")
# setup_target_for_coverage_gcovr_html(
# NAME coverage
# EXECUTABLE testrunner
# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src"
# EXCLUDE "dir2/*")
#
# 5. Use the functions described below to create a custom make target which
# runs your test executable and produces a code coverage report.
#
# 6. Build a Debug build:
# cmake -DCMAKE_BUILD_TYPE=Debug ..
# make
# make my_coverage_target
#
include(CMakeParseArguments)
option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE)
# Check prereqs
find_program( GCOV_PATH gcov )
find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl)
find_program( FASTCOV_PATH NAMES fastcov fastcov.py )
find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat )
find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
find_program( CPPFILT_PATH NAMES c++filt )
if(NOT GCOV_PATH)
message(FATAL_ERROR "gcov not found! Aborting...")
endif() # NOT GCOV_PATH
get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
list(GET LANGUAGES 0 LANG)
if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3)
message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
endif()
elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang")
# Do nothing; exit conditional without error if true
elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU")
# Do nothing; exit conditional without error if true
else()
message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
endif()
endif()
set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage"
CACHE INTERNAL "")
if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path)
if(HAVE_fprofile_abs_path)
set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
endif()
endif()
set(CMAKE_Fortran_FLAGS_COVERAGE
${COVERAGE_COMPILER_FLAGS}
CACHE STRING "Flags used by the Fortran compiler during coverage builds."
FORCE )
set(CMAKE_CXX_FLAGS_COVERAGE
${COVERAGE_COMPILER_FLAGS}
CACHE STRING "Flags used by the C++ compiler during coverage builds."
FORCE )
set(CMAKE_C_FLAGS_COVERAGE
${COVERAGE_COMPILER_FLAGS}
CACHE STRING "Flags used by the C compiler during coverage builds."
FORCE )
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
""
CACHE STRING "Flags used for linking binaries during coverage builds."
FORCE )
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
""
CACHE STRING "Flags used by the shared libraries linker during coverage builds."
FORCE )
mark_as_advanced(
CMAKE_Fortran_FLAGS_COVERAGE
CMAKE_CXX_FLAGS_COVERAGE
CMAKE_C_FLAGS_COVERAGE
CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG))
message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
link_libraries(gcov)
endif()
# Defines a target for running and collection code coverage information
# Builds dependencies, runs the given executable and outputs reports.
# NOTE! The executable should always have a ZERO as exit code otherwise
# the coverage generation will not complete.
#
# setup_target_for_coverage_lcov(
# NAME testrunner_coverage # New target name
# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
# DEPENDENCIES testrunner # Dependencies to build first
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
# # to BASE_DIRECTORY, with CMake 3.4+)
# NO_DEMANGLE # Don't demangle C++ symbols
# # even if c++filt is found
# )
function(setup_target_for_coverage_lcov)
set(options NO_DEMANGLE)
set(oneValueArgs BASE_DIRECTORY NAME)
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT LCOV_PATH)
message(FATAL_ERROR "lcov not found! Aborting...")
endif() # NOT LCOV_PATH
if(NOT GENHTML_PATH)
message(FATAL_ERROR "genhtml not found! Aborting...")
endif() # NOT GENHTML_PATH
# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
if(DEFINED Coverage_BASE_DIRECTORY)
get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
else()
set(BASEDIR ${PROJECT_SOURCE_DIR})
endif()
# Collect excludes (CMake 3.4+: Also compute absolute paths)
set(LCOV_EXCLUDES "")
foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES})
if(CMAKE_VERSION VERSION_GREATER 3.4)
get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
endif()
list(APPEND LCOV_EXCLUDES "${EXCLUDE}")
endforeach()
list(REMOVE_DUPLICATES LCOV_EXCLUDES)
# Conditional arguments
if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE})
set(GENHTML_EXTRA_ARGS "--demangle-cpp")
endif()
# Setting up commands which will be run to generate coverage data.
# Cleanup lcov
set(LCOV_CLEAN_CMD
${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory .
-b ${BASEDIR} --zerocounters
)
# Create baseline to make sure untouched files show up in the report
set(LCOV_BASELINE_CMD
${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b
${BASEDIR} -o ${Coverage_NAME}.base
)
# Run tests
set(LCOV_EXEC_TESTS_CMD
${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
)
# Capturing lcov counters and generating report
set(LCOV_CAPTURE_CMD
${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b
${BASEDIR} --capture --output-file ${Coverage_NAME}.capture
)
# add baseline counters
set(LCOV_BASELINE_COUNT_CMD
${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base
-a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total
)
# filter collected data to final coverage report
set(LCOV_FILTER_CMD
${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove
${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info
)
# Generate HTML output
set(LCOV_GEN_HTML_CMD
${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o
${Coverage_NAME} ${Coverage_NAME}.info
)
if(CODE_COVERAGE_VERBOSE)
message(STATUS "Executed command report")
message(STATUS "Command to clean up lcov: ")
string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}")
message(STATUS "${LCOV_CLEAN_CMD_SPACED}")
message(STATUS "Command to create baseline: ")
string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}")
message(STATUS "${LCOV_BASELINE_CMD_SPACED}")
message(STATUS "Command to run the tests: ")
string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}")
message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}")
message(STATUS "Command to capture counters and generate report: ")
string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}")
message(STATUS "${LCOV_CAPTURE_CMD_SPACED}")
message(STATUS "Command to add baseline counters: ")
string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}")
message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}")
message(STATUS "Command to filter collected data: ")
string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}")
message(STATUS "${LCOV_FILTER_CMD_SPACED}")
message(STATUS "Command to generate lcov HTML output: ")
string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}")
message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}")
endif()
# Setup target
add_custom_target(${Coverage_NAME}
COMMAND ${LCOV_CLEAN_CMD}
COMMAND ${LCOV_BASELINE_CMD}
COMMAND ${LCOV_EXEC_TESTS_CMD}
COMMAND ${LCOV_CAPTURE_CMD}
COMMAND ${LCOV_BASELINE_COUNT_CMD}
COMMAND ${LCOV_FILTER_CMD}
COMMAND ${LCOV_GEN_HTML_CMD}
# Set output files as GENERATED (will be removed on 'make clean')
BYPRODUCTS
${Coverage_NAME}.base
${Coverage_NAME}.capture
${Coverage_NAME}.total
${Coverage_NAME}.info
${Coverage_NAME}/index.html
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
VERBATIM # Protect arguments to commands
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
)
# Show where to find the lcov info report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info."
)
# Show info where to find the report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
)
endfunction() # setup_target_for_coverage_lcov
# Defines a target for running and collection code coverage information
# Builds dependencies, runs the given executable and outputs reports.
# NOTE! The executable should always have a ZERO as exit code otherwise
# the coverage generation will not complete.
#
# setup_target_for_coverage_gcovr_xml(
# NAME ctest_coverage # New target name
# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
# DEPENDENCIES executable_target # Dependencies to build first
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
# # to BASE_DIRECTORY, with CMake 3.4+)
# )
# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the
# GCVOR command.
function(setup_target_for_coverage_gcovr_xml)
set(options NONE)
set(oneValueArgs BASE_DIRECTORY NAME)
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT GCOVR_PATH)
message(FATAL_ERROR "gcovr not found! Aborting...")
endif() # NOT GCOVR_PATH
# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
if(DEFINED Coverage_BASE_DIRECTORY)
get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
else()
set(BASEDIR ${PROJECT_SOURCE_DIR})
endif()
# Collect excludes (CMake 3.4+: Also compute absolute paths)
set(GCOVR_EXCLUDES "")
foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
if(CMAKE_VERSION VERSION_GREATER 3.4)
get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
endif()
list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
endforeach()
list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
# Combine excludes to several -e arguments
set(GCOVR_EXCLUDE_ARGS "")
foreach(EXCLUDE ${GCOVR_EXCLUDES})
list(APPEND GCOVR_EXCLUDE_ARGS "-e")
list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
endforeach()
# Set up commands which will be run to generate coverage data
# Run tests
set(GCOVR_XML_EXEC_TESTS_CMD
${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
)
# Running gcovr
set(GCOVR_XML_CMD
${GCOVR_PATH} --xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} ${GCOVR_EXCLUDE_ARGS}
--object-directory=${PROJECT_BINARY_DIR} -o ${Coverage_NAME}.xml
)
if(CODE_COVERAGE_VERBOSE)
message(STATUS "Executed command report")
message(STATUS "Command to run tests: ")
string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}")
message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}")
message(STATUS "Command to generate gcovr XML coverage data: ")
string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}")
message(STATUS "${GCOVR_XML_CMD_SPACED}")
endif()
add_custom_target(${Coverage_NAME}
COMMAND ${GCOVR_XML_EXEC_TESTS_CMD}
COMMAND ${GCOVR_XML_CMD}
BYPRODUCTS ${Coverage_NAME}.xml
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
VERBATIM # Protect arguments to commands
COMMENT "Running gcovr to produce Cobertura code coverage report."
)
# Show info where to find the report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
)
endfunction() # setup_target_for_coverage_gcovr_xml
# Defines a target for running and collection code coverage information
# Builds dependencies, runs the given executable and outputs reports.
# NOTE! The executable should always have a ZERO as exit code otherwise
# the coverage generation will not complete.
#
# setup_target_for_coverage_gcovr_html(
# NAME ctest_coverage # New target name
# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
# DEPENDENCIES executable_target # Dependencies to build first
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
# # to BASE_DIRECTORY, with CMake 3.4+)
# )
# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the
# GCVOR command.
function(setup_target_for_coverage_gcovr_html)
set(options NONE)
set(oneValueArgs BASE_DIRECTORY NAME)
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT GCOVR_PATH)
message(FATAL_ERROR "gcovr not found! Aborting...")
endif() # NOT GCOVR_PATH
# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
if(DEFINED Coverage_BASE_DIRECTORY)
get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
else()
set(BASEDIR ${PROJECT_SOURCE_DIR})
endif()
# Collect excludes (CMake 3.4+: Also compute absolute paths)
set(GCOVR_EXCLUDES "")
foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
if(CMAKE_VERSION VERSION_GREATER 3.4)
get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
endif()
list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
endforeach()
list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
# Combine excludes to several -e arguments
set(GCOVR_EXCLUDE_ARGS "")
foreach(EXCLUDE ${GCOVR_EXCLUDES})
list(APPEND GCOVR_EXCLUDE_ARGS "-e")
list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
endforeach()
# Set up commands which will be run to generate coverage data
# Run tests
set(GCOVR_HTML_EXEC_TESTS_CMD
${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
)
# Create folder
set(GCOVR_HTML_FOLDER_CMD
${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME}
)
# Running gcovr
set(GCOVR_HTML_CMD
${GCOVR_PATH} --html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR}
-o ${Coverage_NAME}/index.html
)
if(CODE_COVERAGE_VERBOSE)
message(STATUS "Executed command report")
message(STATUS "Command to run tests: ")
string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}")
message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}")
message(STATUS "Command to create a folder: ")
string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}")
message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}")
message(STATUS "Command to generate gcovr HTML coverage data: ")
string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}")
message(STATUS "${GCOVR_HTML_CMD_SPACED}")
endif()
add_custom_target(${Coverage_NAME}
COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD}
COMMAND ${GCOVR_HTML_FOLDER_CMD}
COMMAND ${GCOVR_HTML_CMD}
BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
VERBATIM # Protect arguments to commands
COMMENT "Running gcovr to produce HTML code coverage report."
)
# Show info where to find the report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
)
endfunction() # setup_target_for_coverage_gcovr_html
# Defines a target for running and collection code coverage information
# Builds dependencies, runs the given executable and outputs reports.
# NOTE! The executable should always have a ZERO as exit code otherwise
# the coverage generation will not complete.
#
# setup_target_for_coverage_fastcov(
# NAME testrunner_coverage # New target name
# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
# DEPENDENCIES testrunner # Dependencies to build first
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude.
# NO_DEMANGLE # Don't demangle C++ symbols
# # even if c++filt is found
# SKIP_HTML # Don't create html report
# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths
# )
function(setup_target_for_coverage_fastcov)
set(options NO_DEMANGLE SKIP_HTML)
set(oneValueArgs BASE_DIRECTORY NAME)
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD)
cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT FASTCOV_PATH)
message(FATAL_ERROR "fastcov not found! Aborting...")
endif()
if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH)
message(FATAL_ERROR "genhtml not found! Aborting...")
endif()
# Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
if(Coverage_BASE_DIRECTORY)
get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
else()
set(BASEDIR ${PROJECT_SOURCE_DIR})
endif()
# Collect excludes (Patterns, not paths, for fastcov)
set(FASTCOV_EXCLUDES "")
foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES})
list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}")
endforeach()
list(REMOVE_DUPLICATES FASTCOV_EXCLUDES)
# Conditional arguments
if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE})
set(GENHTML_EXTRA_ARGS "--demangle-cpp")
endif()
# Set up commands which will be run to generate coverage data
set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS})
set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH}
--search-directory ${BASEDIR}
--process-gcno
--output ${Coverage_NAME}.json
--exclude ${FASTCOV_EXCLUDES}
--exclude ${FASTCOV_EXCLUDES}
)
set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH}
-C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info
)
if(Coverage_SKIP_HTML)
set(FASTCOV_HTML_CMD ";")
else()
set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS}
-o ${Coverage_NAME} ${Coverage_NAME}.info
)
endif()
set(FASTCOV_POST_CMD ";")
if(Coverage_POST_CMD)
set(FASTCOV_POST_CMD ${Coverage_POST_CMD})
endif()
if(CODE_COVERAGE_VERBOSE)
message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):")
message(" Running tests:")
string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}")
message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}")
message(" Capturing fastcov counters and generating report:")
string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}")
message(" ${FASTCOV_CAPTURE_CMD_SPACED}")
message(" Converting fastcov .json to lcov .info:")
string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}")
message(" ${FASTCOV_CONVERT_CMD_SPACED}")
if(NOT Coverage_SKIP_HTML)
message(" Generating HTML report: ")
string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}")
message(" ${FASTCOV_HTML_CMD_SPACED}")
endif()
if(Coverage_POST_CMD)
message(" Running post command: ")
string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}")
message(" ${FASTCOV_POST_CMD_SPACED}")
endif()
endif()
# Setup target
add_custom_target(${Coverage_NAME}
# Cleanup fastcov
COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH}
--search-directory ${BASEDIR}
--zerocounters
COMMAND ${FASTCOV_EXEC_TESTS_CMD}
COMMAND ${FASTCOV_CAPTURE_CMD}
COMMAND ${FASTCOV_CONVERT_CMD}
COMMAND ${FASTCOV_HTML_CMD}
COMMAND ${FASTCOV_POST_CMD}
# Set output files as GENERATED (will be removed on 'make clean')
BYPRODUCTS
${Coverage_NAME}.info
${Coverage_NAME}.json
${Coverage_NAME}/index.html # report directory
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
VERBATIM # Protect arguments to commands
COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report."
)
set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.")
if(NOT Coverage_SKIP_HTML)
string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.")
endif()
# Show where to find the fastcov info report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG}
)
endfunction() # setup_target_for_coverage_fastcov
function(append_coverage_compiler_flags)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
endfunction() # append_coverage_compiler_flags
+18
View File
@@ -0,0 +1,18 @@
# Copyright (c) 2016, Ruslan Baratov
#
# Licensed under the MIT License (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
#
# http://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
@PACKAGE_INIT@
find_package(Threads REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake")
check_required_components("@PROJECT_NAME@")
@@ -0,0 +1,4 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="515px" height="315px" viewBox="0 0 515 315" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
<title>Artboard Copy</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="Artboard-Copy" stroke-width="3">
<g id="Group" transform="translate(55.000000, 70.000000)">
<polyline id="Path-4" stroke="#F3A530" points="7.5 118.5 48.5 44.5 191.5 170 355 87"></polyline>
<polyline id="Path-5" stroke="#30499B" points="0.5 62 113 160.5 355 0"></polyline>
<polyline id="Path-6" stroke="#4CB2D4" points="42 146.5 194 39 406 175.5"></polyline>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 968 B

@@ -0,0 +1,318 @@
#ifndef STATSD_CLIENT_HPP
#define STATSD_CLIENT_HPP
#include <cpp-statsd-client/UDPSender.hpp>
#include <cstdint>
#include <cstdio>
#include <iomanip>
#include <memory>
#include <random>
#include <sstream>
#include <string>
#include <vector>
namespace Statsd {
/*!
*
* Statsd client
*
* This is the Statsd client, exposing the classic methods
* and relying on a UDP sender for the actual sending.
*
* The prefix for a stat is provided once at construction or
* on reconfiguring the client. The separator character '.'
* is automatically inserted between the prefix and the stats
* key, therefore you should neither append one to the prefix
* nor prepend one to the key
*
* The sampling frequency is specified per call and uses a
* random number generator to determine whether or not the stat
* will be recorded this time or not.
*
* The top level configuration includes 2 optional parameters
* that determine how the stats are delivered to statsd. These
* parameters are the batching size and the send interval.
*
* The batching size controls the number of bytes to send
* in each UDP datagram to statsd. This is not a hard limit as
* we continue appending to a batch of stats until the limit
* has been reached or surpassed. When this occurs we add the
* batch to a queue and create a new batch to appended to. A
* value of 0 for the batching size will disable batching such
* that each stat will be sent to the daemon individually.
*
* The send interval controls the rate at which queued batches
* of stats will be sent to statsd. If batching is disabled,
* this value is ignored and each individual stat is sent to
* statsd immediately in a blocking fashion. If batching is
* enabled (ie. non-zero) then the send interval is the number
* of milliseconds to wait before flushing the queue of
* batched stats messages to the daemon. This is done in a non-
* blocking fashion via a background thread. If the send
* interval is 0 then the stats messages are appended to a
* queue until the caller manually flushes the queue via the
* flush method.
*
*/
class StatsdClient {
public:
//!@name Constructor and destructor, non-copyable
//!@{
//! Constructor
StatsdClient(const std::string& host,
const uint16_t port,
const std::string& prefix,
const uint64_t batchsize = 0,
const uint64_t sendInterval = 1000,
const unsigned int gaugePrecision = 4) noexcept;
StatsdClient(const StatsdClient&) = delete;
StatsdClient& operator=(const StatsdClient&) = delete;
//!@}
//!@name Methods
//!@{
//! Sets a configuration { host, port, prefix, batchsize }
void setConfig(const std::string& host,
const uint16_t port,
const std::string& prefix,
const uint64_t batchsize = 0,
const uint64_t sendInterval = 1000,
const unsigned int gaugePrecision = 4) noexcept;
//! Returns the error message as an std::string
const std::string& errorMessage() const noexcept;
//! Increments the key, at a given frequency rate
void increment(const std::string& key,
float frequency = 1.0f,
const std::vector<std::string>& tags = {}) const noexcept;
//! Increments the key, at a given frequency rate
void decrement(const std::string& key,
float frequency = 1.0f,
const std::vector<std::string>& tags = {}) const noexcept;
//! Adjusts the specified key by a given delta, at a given frequency rate
void count(const std::string& key,
const int delta,
float frequency = 1.0f,
const std::vector<std::string>& tags = {}) const noexcept;
//! Records a gauge for the key, with a given value, at a given frequency rate
template <typename T>
void gauge(const std::string& key,
const T value,
float frequency = 1.0f,
const std::vector<std::string>& tags = {}) const noexcept;
//! Records a timing for a key, at a given frequency
void timing(const std::string& key,
const unsigned int ms,
float frequency = 1.0f,
const std::vector<std::string>& tags = {}) const noexcept;
//! Records a count of unique occurrences for a key, at a given frequency
void set(const std::string& key,
const unsigned int sum,
float frequency = 1.0f,
const std::vector<std::string>& tags = {}) const noexcept;
//! Seed the RNG that controls sampling
void seed(unsigned int seed = std::random_device()()) noexcept;
//! Flush any queued stats to the daemon
void flush() noexcept;
//!@}
private:
// @name Private methods
// @{
//! Send a value for a key, according to its type, at a given frequency
template <typename T>
void send(const std::string& key,
const T value,
const char* type,
float frequency,
const std::vector<std::string>& tags) const noexcept;
//!@}
private:
//! The prefix to be used for metrics
std::string m_prefix;
//! The UDP sender to be used for actual sending
std::unique_ptr<UDPSender> m_sender;
//! The random number generator for handling sampling
mutable std::mt19937 m_randomEngine;
//! The buffer string format our stats before sending them
mutable std::string m_buffer;
//! Fixed floating point precision of gauges
unsigned int m_gaugePrecision;
};
namespace detail {
inline std::string sanitizePrefix(std::string prefix) {
// For convenience we provide the dot when generating the stat message
if (!prefix.empty() && prefix.back() == '.') {
prefix.pop_back();
}
return prefix;
}
// All supported metric types
constexpr char METRIC_TYPE_COUNT[] = "c";
constexpr char METRIC_TYPE_GAUGE[] = "g";
constexpr char METRIC_TYPE_TIMING[] = "ms";
constexpr char METRIC_TYPE_SET[] = "s";
} // namespace detail
inline StatsdClient::StatsdClient(const std::string& host,
const uint16_t port,
const std::string& prefix,
const uint64_t batchsize,
const uint64_t sendInterval,
const unsigned int gaugePrecision) noexcept
: m_prefix(detail::sanitizePrefix(prefix)),
m_sender(new UDPSender{host, port, batchsize, sendInterval}),
m_gaugePrecision(gaugePrecision) {
// Initialize the random generator to be used for sampling
seed();
// Avoid re-allocations by reserving a generous buffer
m_buffer.reserve(256);
}
inline void StatsdClient::setConfig(const std::string& host,
const uint16_t port,
const std::string& prefix,
const uint64_t batchsize,
const uint64_t sendInterval,
const unsigned int gaugePrecision) noexcept {
m_prefix = detail::sanitizePrefix(prefix);
m_sender.reset(new UDPSender(host, port, batchsize, sendInterval));
m_gaugePrecision = gaugePrecision;
}
inline const std::string& StatsdClient::errorMessage() const noexcept {
return m_sender->errorMessage();
}
inline void StatsdClient::decrement(const std::string& key,
float frequency,
const std::vector<std::string>& tags) const noexcept {
count(key, -1, frequency, tags);
}
inline void StatsdClient::increment(const std::string& key,
float frequency,
const std::vector<std::string>& tags) const noexcept {
count(key, 1, frequency, tags);
}
inline void StatsdClient::count(const std::string& key,
const int delta,
float frequency,
const std::vector<std::string>& tags) const noexcept {
send(key, delta, detail::METRIC_TYPE_COUNT, frequency, tags);
}
template <typename T>
inline void StatsdClient::gauge(const std::string& key,
const T value,
const float frequency,
const std::vector<std::string>& tags) const noexcept {
send(key, value, detail::METRIC_TYPE_GAUGE, frequency, tags);
}
inline void StatsdClient::timing(const std::string& key,
const unsigned int ms,
float frequency,
const std::vector<std::string>& tags) const noexcept {
send(key, ms, detail::METRIC_TYPE_TIMING, frequency, tags);
}
inline void StatsdClient::set(const std::string& key,
const unsigned int sum,
float frequency,
const std::vector<std::string>& tags) const noexcept {
send(key, sum, detail::METRIC_TYPE_SET, frequency, tags);
}
template <typename T>
inline void StatsdClient::send(const std::string& key,
const T value,
const char* type,
float frequency,
const std::vector<std::string>& tags) const noexcept {
// Bail if we can't send anything anyway
if (!m_sender->initialized()) {
return;
}
// A valid frequency is: 0 <= f <= 1
// At 0 you never emit the stat, at 1 you always emit the stat and with anything else you roll the dice
frequency = std::max(std::min(frequency, 1.f), 0.f);
constexpr float epsilon{0.0001f};
const bool isFrequencyOne = std::fabs(frequency - 1.0f) < epsilon;
const bool isFrequencyZero = std::fabs(frequency) < epsilon;
if (isFrequencyZero ||
(!isFrequencyOne && (frequency < std::uniform_real_distribution<float>(0.f, 1.f)(m_randomEngine)))) {
return;
}
// Format the stat message
std::stringstream valueStream;
valueStream << std::fixed << std::setprecision(m_gaugePrecision) << value;
m_buffer.clear();
m_buffer.append(m_prefix);
if (!m_prefix.empty() && !key.empty()) {
m_buffer.push_back('.');
}
m_buffer.append(key);
m_buffer.push_back(':');
m_buffer.append(valueStream.str());
m_buffer.push_back('|');
m_buffer.append(type);
if (frequency < 1.f) {
m_buffer.append("|@0.");
m_buffer.append(std::to_string(static_cast<int>(frequency * 100)));
}
if (!tags.empty()) {
m_buffer.append("|#");
for (const auto& tag : tags) {
m_buffer.append(tag);
m_buffer.push_back(',');
}
m_buffer.pop_back();
}
// Send the message via the UDP sender
m_sender->send(m_buffer);
}
inline void StatsdClient::seed(unsigned int seed) noexcept {
m_randomEngine.seed(seed);
}
inline void StatsdClient::flush() noexcept {
m_sender->flush();
}
} // namespace Statsd
#endif
@@ -0,0 +1,345 @@
#ifndef UDP_SENDER_HPP
#define UDP_SENDER_HPP
#ifdef _WIN32
#define NOMINMAX
#include <io.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#include <atomic>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <deque>
#include <mutex>
#include <string>
#include <thread>
namespace Statsd {
#ifdef _WIN32
using SOCKET_TYPE = SOCKET;
constexpr SOCKET_TYPE k_invalidSocket{INVALID_SOCKET};
#define SOCKET_ERRNO WSAGetLastError()
#define SOCKET_CLOSE closesocket
#else
using SOCKET_TYPE = int;
constexpr SOCKET_TYPE k_invalidSocket{-1};
#define SOCKET_ERRNO errno
#define SOCKET_CLOSE close
#endif
/*!
*
* UDP sender
*
* A simple UDP sender handling batching.
*
*/
class UDPSender final {
public:
//!@name Constructor and destructor, non-copyable
//!@{
//! Constructor
UDPSender(const std::string& host,
const uint16_t port,
const uint64_t batchsize,
const uint64_t sendInterval) noexcept;
//! Destructor
~UDPSender();
UDPSender(const UDPSender&) = delete;
UDPSender& operator=(const UDPSender&) = delete;
UDPSender(UDPSender&&) = delete;
//!@}
//!@name Methods
//!@{
//! Send or enqueue a message
void send(const std::string& message) noexcept;
//! Returns the error message as a string
const std::string& errorMessage() const noexcept;
//! Returns true if the sender is initialized
bool initialized() const noexcept;
//! Flushes any queued messages
void flush() noexcept;
//!@}
private:
// @name Private methods
// @{
//! Initialize the sender and returns true when it is initialized
bool initialize() noexcept;
//! Queue a message to be sent to the daemon later
inline void queueMessage(const std::string& message) noexcept;
//! Send a message to the daemon
void sendToDaemon(const std::string& message) noexcept;
//!@}
private:
// @name State variables
// @{
//! Shall we exit?
std::atomic<bool> m_mustExit{false};
//!@}
// @name Network info
// @{
//! The hostname
std::string m_host;
//! The port
uint16_t m_port;
//! The structure holding the server
struct sockaddr_in m_server;
//! The socket to be used
SOCKET_TYPE m_socket = k_invalidSocket;
//!@}
// @name Batching info
// @{
//! The batching size
uint64_t m_batchsize;
//! The sending frequency in milliseconds
uint64_t m_sendInterval;
//! The queue batching the messages
std::deque<std::string> m_batchingMessageQueue;
//! The mutex used for batching
std::mutex m_batchingMutex;
//! The thread dedicated to the batching
std::thread m_batchingThread;
//!@}
//! Error message (optional string)
std::string m_errorMessage;
};
namespace detail {
inline bool isValidSocket(const SOCKET_TYPE socket) {
return socket != k_invalidSocket;
}
#ifdef _WIN32
struct WinSockSingleton {
inline static const WinSockSingleton& getInstance() {
static const WinSockSingleton instance;
return instance;
}
inline bool ok() const {
return m_ok;
}
~WinSockSingleton() {
WSACleanup();
}
private:
WinSockSingleton() {
WSADATA wsa;
m_ok = WSAStartup(MAKEWORD(2, 2), &wsa) == 0;
}
bool m_ok;
};
#endif
} // namespace detail
inline UDPSender::UDPSender(const std::string& host,
const uint16_t port,
const uint64_t batchsize,
const uint64_t sendInterval) noexcept
: m_host(host), m_port(port), m_batchsize(batchsize), m_sendInterval(sendInterval) {
// Initialize the socket
if (!initialize()) {
return;
}
// If batching is on, use a dedicated thread to send after the wait time is reached
if (m_batchsize != 0 && m_sendInterval > 0) {
// Define the batching thread
m_batchingThread = std::thread([this] {
// TODO: this will drop unsent stats, should we send all the unsent stats before we exit?
while (!m_mustExit.load(std::memory_order_acquire)) {
std::deque<std::string> stagedMessageQueue;
std::unique_lock<std::mutex> batchingLock(m_batchingMutex);
m_batchingMessageQueue.swap(stagedMessageQueue);
batchingLock.unlock();
// Flush the queue
while (!stagedMessageQueue.empty()) {
sendToDaemon(stagedMessageQueue.front());
stagedMessageQueue.pop_front();
}
// Wait before sending the next batch
std::this_thread::sleep_for(std::chrono::milliseconds(m_sendInterval));
}
});
}
}
inline UDPSender::~UDPSender() {
if (!initialized()) {
return;
}
// If we're running a background thread tell it to stop
if (m_batchingThread.joinable()) {
m_mustExit.store(true, std::memory_order_release);
m_batchingThread.join();
}
// Cleanup the socket
SOCKET_CLOSE(m_socket);
}
inline void UDPSender::send(const std::string& message) noexcept {
m_errorMessage.clear();
// If batching is on, accumulate messages in the queue
if (m_batchsize > 0) {
queueMessage(message);
return;
}
// Or send it right now
sendToDaemon(message);
}
inline void UDPSender::queueMessage(const std::string& message) noexcept {
// We aquire a lock but only if we actually need to (i.e. there is a thread also accessing the queue)
auto batchingLock =
m_batchingThread.joinable() ? std::unique_lock<std::mutex>(m_batchingMutex) : std::unique_lock<std::mutex>();
// Either we don't have a place to batch our message or we exceeded the batch size, so make a new batch
if (m_batchingMessageQueue.empty() || m_batchingMessageQueue.back().length() > m_batchsize) {
m_batchingMessageQueue.emplace_back();
m_batchingMessageQueue.back().reserve(m_batchsize + 256);
} // When there is already a batch open we need a separator when its not empty
else if (!m_batchingMessageQueue.back().empty()) {
m_batchingMessageQueue.back().push_back('\n');
}
// Add the new message to the batch
m_batchingMessageQueue.back().append(message);
}
inline const std::string& UDPSender::errorMessage() const noexcept {
return m_errorMessage;
}
inline bool UDPSender::initialize() noexcept {
#ifdef _WIN32
if (!detail::WinSockSingleton::getInstance().ok()) {
m_errorMessage = "WSAStartup failed: errno=" + std::to_string(SOCKET_ERRNO);
}
#endif
// Connect the socket
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (!detail::isValidSocket(m_socket)) {
m_errorMessage = "socket creation failed: errno=" + std::to_string(SOCKET_ERRNO);
return false;
}
std::memset(&m_server, 0, sizeof(m_server));
m_server.sin_family = AF_INET;
m_server.sin_port = htons(m_port);
if (inet_pton(AF_INET, m_host.c_str(), &m_server.sin_addr) == 0) {
// An error code has been returned by inet_aton
// Specify the criteria for selecting the socket address structure
struct addrinfo hints;
std::memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
// Get the address info using the hints
struct addrinfo* results = nullptr;
const int ret{getaddrinfo(m_host.c_str(), nullptr, &hints, &results)};
if (ret != 0) {
// An error code has been returned by getaddrinfo
SOCKET_CLOSE(m_socket);
m_socket = k_invalidSocket;
m_errorMessage = "getaddrinfo failed: err=" + std::to_string(ret) + ", msg=" + gai_strerror(ret);
return false;
}
// Copy the results in m_server
struct sockaddr_in* host_addr = (struct sockaddr_in*)results->ai_addr;
std::memcpy(&m_server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr));
// Free the memory allocated
freeaddrinfo(results);
}
return true;
}
inline void UDPSender::sendToDaemon(const std::string& message) noexcept {
// Try sending the message
const auto ret = sendto(m_socket,
message.data(),
#ifdef _WIN32
static_cast<int>(message.size()),
#else
message.size(),
#endif
0,
(struct sockaddr*)&m_server,
sizeof(m_server));
if (ret == -1) {
m_errorMessage = "sendto server failed: host=" + m_host + ":" + std::to_string(m_port) +
", err=" + std::to_string(SOCKET_ERRNO);
}
}
inline bool UDPSender::initialized() const noexcept {
return m_socket != k_invalidSocket;
}
inline void UDPSender::flush() noexcept {
// We aquire a lock but only if we actually need to (ie there is a thread also accessing the queue)
auto batchingLock =
m_batchingThread.joinable() ? std::unique_lock<std::mutex>(m_batchingMutex) : std::unique_lock<std::mutex>();
// Flush the queue
while (!m_batchingMessageQueue.empty()) {
sendToDaemon(m_batchingMessageQueue.front());
m_batchingMessageQueue.pop_front();
}
}
} // namespace Statsd
#endif
+80
View File
@@ -0,0 +1,80 @@
#ifndef STATSD_SERVER_HPP
#define STATSD_SERVER_HPP
// It might make sense to include this test class in the UDPSender header
// it includes most of the cross platform defines etc that we need for socket io
#include "cpp-statsd-client/UDPSender.hpp"
#include <algorithm>
#include <string>
namespace Statsd {
class StatsdServer {
public:
StatsdServer(unsigned short port = 8125) noexcept {
#ifdef _WIN32
if (!detail::WinSockSingleton::getInstance().ok()) {
m_errorMessage = "WSAStartup failed: errno=" + std::to_string(SOCKET_ERRNO);
}
#endif
// Create the socket
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (!detail::isValidSocket(m_socket)) {
m_errorMessage = "socket creation failed: errno=" + std::to_string(SOCKET_ERRNO);
return;
}
// Binding should be with ipv4 to all interfaces
struct sockaddr_in address {};
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = INADDR_ANY;
// Try to bind
if (bind(m_socket, reinterpret_cast<const struct sockaddr*>(&address), sizeof(address)) != 0) {
SOCKET_CLOSE(m_socket);
m_socket = k_invalidSocket;
m_errorMessage = "bind failed: errno=" + std::to_string(SOCKET_ERRNO);
}
}
~StatsdServer() {
if (detail::isValidSocket(m_socket)) {
SOCKET_CLOSE(m_socket);
}
}
const std::string& errorMessage() const noexcept {
return m_errorMessage;
}
std::string receive() noexcept {
// If uninitialized then bail
if (!detail::isValidSocket(m_socket)) {
return "";
}
// Try to receive (this is blocking)
std::string buffer(256, '\0');
int string_len;
if ((string_len = recv(m_socket, &buffer[0], static_cast<int>(buffer.size()), 0)) < 1) {
m_errorMessage = "Could not recv on the socket file descriptor";
return "";
}
// No error return the trimmed result
m_errorMessage.clear();
buffer.resize(std::min(static_cast<size_t>(string_len), buffer.size()));
return buffer;
}
private:
SOCKET_TYPE m_socket;
std::string m_errorMessage;
};
} // namespace Statsd
#endif
+184
View File
@@ -0,0 +1,184 @@
#include <iostream>
#include "StatsdServer.hpp"
#include "cpp-statsd-client/StatsdClient.hpp"
using namespace Statsd;
// Each test suite below spawns a thread to recv the client messages over UDP as if it were a real statsd server
// Note that we could just synchronously recv metrics and not use a thread but doing the test async has the
// advantage that we can test the threaded batching mode in a straightforward way. The server thread basically
// just keeps storing metrics in an vector until it hears a special one signaling the test is over and bails
void mock(StatsdServer& server, std::vector<std::string>& messages) {
do {
// Grab the messages that are waiting
auto recvd = server.receive();
// Split the messages on '\n'
auto start = std::string::npos;
do {
// Keep this message
auto end = recvd.find('\n', ++start);
messages.emplace_back(recvd.substr(start, end));
start = end;
// Bail if we found the special quit message
if (messages.back().find("DONE") != std::string::npos) {
messages.pop_back();
return;
}
} while (start != std::string::npos);
} while (server.errorMessage().empty() && !messages.back().empty());
}
template <typename SocketWrapper>
void throwOnError(const SocketWrapper& wrapped, bool expectEmpty = true, const std::string& extraMessage = "") {
if (wrapped.errorMessage().empty() != expectEmpty) {
std::cerr << (expectEmpty ? wrapped.errorMessage() : extraMessage) << std::endl;
throw std::runtime_error(expectEmpty ? wrapped.errorMessage() : extraMessage);
}
}
void throwOnWrongMessage(StatsdServer& server, const std::string& expected) {
auto actual = server.receive();
if (actual != expected) {
std::cerr << "Expected: " << expected << " but got: " << actual << std::endl;
throw std::runtime_error("Incorrect stat received");
}
}
void testErrorConditions() {
// Resolve a rubbish ip and make sure initialization failed
StatsdClient client{"256.256.256.256", 8125, "myPrefix", 20};
throwOnError(client, false, "Should not be able to resolve a ridiculous ip");
}
void testReconfigure() {
StatsdServer server;
throwOnError(server);
StatsdClient client("localhost", 8125, "first.");
client.increment("foo");
throwOnWrongMessage(server, "first.foo:1|c");
client.setConfig("localhost", 8125, "second");
client.increment("bar");
throwOnWrongMessage(server, "second.bar:1|c");
client.setConfig("localhost", 8125, "");
client.increment("third.baz");
throwOnWrongMessage(server, "third.baz:1|c");
client.increment("");
throwOnWrongMessage(server, ":1|c");
// TODO: test what happens with the batching after resolving the question about incomplete
// batches being dropped vs sent on reconfiguring
}
void testSendRecv(uint64_t batchSize, uint64_t sendInterval) {
StatsdServer mock_server;
std::vector<std::string> messages, expected;
std::thread server(mock, std::ref(mock_server), std::ref(messages));
// Set a new config that has the client send messages to a proper address that can be resolved
StatsdClient client("localhost", 8125, "sendRecv.", batchSize, sendInterval, 3);
throwOnError(client);
// TODO: I forget if we need to wait for the server to be ready here before sending the first stats
// is there a race condition where the client sending before the server binds would drop that clients message
for (int i = 0; i < 3; ++i) {
// Increment "coco"
client.increment("coco");
throwOnError(client);
expected.emplace_back("sendRecv.coco:1|c");
// Decrement "kiki"
client.decrement("kiki");
throwOnError(client);
expected.emplace_back("sendRecv.kiki:-1|c");
// Adjusts "toto" by +2
client.seed(19); // this seed gets a hit on the first call
client.count("toto", 2, 0.1f);
throwOnError(client);
expected.emplace_back("sendRecv.toto:2|c|@0.10");
// Gets "sampled out" by the random number generator
client.count("popo", 9, 0.1f);
throwOnError(client);
// Record a gauge "titi" to 3
client.gauge("titi", 3);
throwOnError(client);
expected.emplace_back("sendRecv.titi:3|g");
// Record a gauge "titifloat" to -123.456789 with precision 3
client.gauge("titifloat", -123.456789);
throwOnError(client);
expected.emplace_back("sendRecv.titifloat:-123.457|g");
// Record a timing of 2ms for "myTiming"
client.seed(19);
client.timing("myTiming", 2, 0.1f);
throwOnError(client);
expected.emplace_back("sendRecv.myTiming:2|ms|@0.10");
// Send a set with 1227 total uniques
client.set("tutu", 1227, 2.0f);
throwOnError(client);
expected.emplace_back("sendRecv.tutu:1227|s");
// Gauge but with tags
client.gauge("dr.röstigrabe", 333, 1.f, {"liegt", "im", "weste"});
throwOnError(client);
expected.emplace_back("sendRecv.dr.röstigrabe:333|g|#liegt,im,weste");
// All the things
client.count("foo", -42, .9f, {"bar", "baz"});
throwOnError(client);
expected.emplace_back("sendRecv.foo:-42|c|@0.90|#bar,baz");
}
// Signal the mock server we are done
client.timing("DONE", 0);
// If manual flushing do it now
if (sendInterval == 0) {
client.flush();
}
// Wait for the server to stop
server.join();
// Make sure we get the exactly correct output
if (messages != expected) {
std::cerr << "Unexpected stats received by server, got:" << std::endl;
for (const auto& message : messages) {
std::cerr << message << std::endl;
}
std::cerr << std::endl << "But we expected:" << std::endl;
for (const auto& message : expected) {
std::cerr << message << std::endl;
}
throw std::runtime_error("Unexpected stats");
}
}
int main() {
// If any of these tests fail they throw an exception, not catching makes for a nonzero return code
// general things that should be errors
testErrorConditions();
// reconfiguring how you are sending
testReconfigure();
// no batching
testSendRecv(0, 0);
// background batching
testSendRecv(32, 1000);
// manual flushing of batches
testSendRecv(16, 0);
return EXIT_SUCCESS;
}
+1 -1
+18
View File
@@ -1145,6 +1145,11 @@ acllog-max-len 128
# #
# active-expire-effort 1 # active-expire-effort 1
# Force evictions when used system memory reaches X% of total system memory.
# This is useful as a safeguard to prevent OOM kills (0 to disable).
#
# force-eviction-percent 0
############################# LAZY FREEING #################################### ############################# LAZY FREEING ####################################
# KeyDB 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
@@ -2088,3 +2093,16 @@ active-client-balancing yes
# disk space or any other I/O error KeyDB will instead use memory. # disk space or any other I/O error KeyDB will instead use memory.
# #
# blob-support false # blob-support false
# Begin load shedding if we use more than X% CPU relative to the number of server threads
# E.g. if overload-protect-percent is set to 80 and there are 8 server-threads, then the
# actual CPU protection will be 8 * 100 * 0.80 = 640% CPU usage.
#
# Set to 0 to disable
# overload-protect-percent 0
# Inform KeyDB of the availability zone if running in a cloud environment. Currently
# this is only exposed via the info command for clients to use, but in the future we
# we may also use this when making decisions for replication.
#
# availability-zone "us-east-1a"
+114
View File
@@ -0,0 +1,114 @@
FROM ubuntu:20.04
SHELL ["/bin/bash","-c"]
RUN groupadd -r keydb && useradd -r -g keydb keydb
# use gosu for easy step-down from root: https://github.com/tianon/gosu/releases
ENV GOSU_VERSION 1.14
RUN set -eux; \
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get -o Dpkg::Options::="--force-confnew" install -y --no-install-recommends ca-certificates dirmngr gnupg wget; \
rm -rf /var/lib/apt/lists/*; \
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
export GNUPGHOME="$(mktemp -d)"; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
gpgconf --kill all; \
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
chmod +x /usr/local/bin/gosu; \
gosu --version; \
gosu nobody true
# build KeyDB
ARG MAKE_JOBS=""
ARG ENABLE_FLASH=""
COPY . /tmp/keydb-internal
RUN set -eux; \
cd /tmp/keydb-internal; \
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confnew" install -qqy --no-install-recommends \
dpkg-dev \
pkg-config \
ca-certificates \
build-essential \
nasm \
autotools-dev \
autoconf \
libjemalloc-dev \
tcl \
tcl-dev \
uuid-dev \
libcurl4-openssl-dev \
libbz2-dev \
libzstd-dev \
liblz4-dev \
libsnappy-dev \
libssl-dev \
git; \
# disable protected mode as it relates to docker
grep -E '^ *createBoolConfig[(]"protected-mode",.*, *1 *,.*[)],$' ./src/config.cpp; \
sed -ri 's!^( *createBoolConfig[(]"protected-mode",.*, *)1( *,.*[)],)$!\10\2!' ./src/config.cpp; \
grep -E '^ *createBoolConfig[(]"protected-mode",.*, *0 *,.*[)],$' ./src/config.cpp; \
make distclean; \
make -j$([ -z "$MAKE_JOBS" ] && nproc || echo "$MAKE_JOBS") BUILD_TLS=yes NO_LICENSE_CHECK=yes $([ -z "$ENABLE_FLASH" ] && echo "" || echo "ENABLE_FLASH=$ENABLE_FLASH"); \
cd src; \
mv modules/keydb_modstatsd/modstatsd.so /usr/local/lib/; \
strip keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel; \
mv keydb-server keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel /usr/local/bin/; \
# clean up unused dependencies
echo $savedAptMark; \
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \
find /usr/local -type f -executable -exec ldd '{}' ';' \
| awk '/=>/ { print $(NF-1) }' \
| sed 's:.*/::' \
| sort -u \
| xargs -r dpkg-query --search \
| cut -d: -f1 \
| sort -u \
| xargs -r apt-mark manual \
; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*; \
# create working directories and organize files
RUN \
mkdir /data && chown keydb:keydb /data; \
mkdir /flash && chown keydb:keydb /flash; \
mkdir -p /etc/keydb; \
cp /tmp/keydb-internal/keydb.conf /etc/keydb/; \
sed -i 's/^\(daemonize .*\)$/# \1/' /etc/keydb/keydb.conf; \
sed -i 's/^\(dir .*\)$/# \1\ndir \/data/' /etc/keydb/keydb.conf; \
sed -i 's/^\(logfile .*\)$/# \1/' /etc/keydb/keydb.conf; \
sed -i 's/protected-mode yes/protected-mode no/g' /etc/keydb/keydb.conf; \
sed -i 's/^\(bind .*\)$/# \1/' /etc/keydb/keydb.conf; \
echo -e "\nloadmodule /usr/local/lib/modstatsd.so" >> /etc/keydb/keydb.conf; \
ln -s keydb-cli redis-cli; \
cd /etc/keydb; \
ln -s keydb.conf redis.conf; \
rm -rf /tmp/*
# generate entrypoint script
RUN set -eux; \
echo '#!/bin/sh' > /usr/local/bin/docker-entrypoint.sh; \
echo 'set -e' >> /usr/local/bin/docker-entrypoint.sh; \
echo "# perpend 'keydb-server' if not provided as first argument" >> /usr/local/bin/docker-entrypoint.sh; \
echo 'if [ "${1}" != "keydb-server" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \
echo ' set -- keydb-server "$@"' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'fi' >> /usr/local/bin/docker-entrypoint.sh; \
echo "# allow the container to be started with `--user`" >> /usr/local/bin/docker-entrypoint.sh; \
echo 'if [ "$1" = "keydb-server" -a "$(id -u)" = "0" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \
echo " find . \! -user keydb -exec chown keydb '{}' +" >> /usr/local/bin/docker-entrypoint.sh; \
echo ' exec gosu keydb "$0" "$@"' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'fi' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'exec "$@"' >> /usr/local/bin/docker-entrypoint.sh; \
chmod +x /usr/local/bin/docker-entrypoint.sh
# set remaining image properties
VOLUME /data
WORKDIR /data
ENV KEYDB_PRO_DIRECTORY=/usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD ["keydb-server","/etc/keydb/keydb.conf"]
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
# make the build
git submodule init && git submodule update
make BUILD_TLS=yes ENABLE_FLASH=yes -j$(nproc) KEYDB_CFLAGS='-Werror' KEYDB_CXXFLAGS='-Werror'
# gen-cert
./utils/gen-test-certs.sh
@@ -1,84 +0,0 @@
From: Chris Lamb <lamby@debian.org>
Date: Wed, 3 Oct 2018 19:24:16 +0100
Subject: Add support for a USE_SYSTEM_HIREDIS flag.
---
deps/Makefile | 2 ++
src/Makefile | 19 ++++++++++++++-----
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/deps/Makefile b/deps/Makefile
index 2ed7736..d6b8e3e 100644
--- a/deps/Makefile
+++ b/deps/Makefile
@@ -33,7 +33,9 @@ ifneq ($(shell sh -c '[ -f .make-ldflags ] && cat .make-ldflags || echo none'),
endif
distclean:
+ifneq ($(USE_SYSTEM_HIREDIS),yes)
-(cd hiredis && $(MAKE) clean) > /dev/null || true
+endif
-(cd linenoise && $(MAKE) clean) > /dev/null || true
ifneq ($(USE_SYSTEM_LUA),yes)
-(cd lua && $(MAKE) clean) > /dev/null || true
diff --git a/src/Makefile b/src/Makefile
index 49085f2..9e7e6f1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -16,7 +16,7 @@ release_hdr := $(shell sh -c './mkreleasehdr.sh')
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
OPTIMIZATION?=-O2
-DEPENDENCY_TARGETS=hiredis linenoise
+DEPENDENCY_TARGETS=linenoise
NODEPS:=clean distclean
# Default settings
@@ -107,7 +107,7 @@ endif
endif
endif
# Include paths to dependencies
-FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise
+FINAL_CFLAGS+= -I../deps/linenoise
ifeq ($(MALLOC),tcmalloc)
FINAL_CFLAGS+= -DUSE_TCMALLOC
@@ -139,6 +139,15 @@ else
FINAL_LIBS := ../deps/lua/src/liblua.a $(FINAL_LIBS)
endif
+ifeq ($(USE_SYSTEM_HIREDIS),yes)
+ FINAL_LIBS+= -lhiredis
+ FINAL_CFLAGS+= -I/usr/include/hiredis
+else
+ DEPENDENCY_TARGETS += hiredis
+ FINAL_LIBS+= ../deps/hiredis/libhiredis.a
+ FINAL_CFLAGS+= -I../deps/hiredis
+endif
+
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) $(CPPFLAGS)
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
@@ -210,7 +219,7 @@ endif
# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
- $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
+ $(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
# redis-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
@@ -226,11 +235,11 @@ $(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME)
# redis-cli
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
- $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)
+ $(REDIS_LD) -o $@ $^ ../deps/linenoise/linenoise.o $(FINAL_LIBS)
# redis-benchmark
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
- $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
+ $(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
dict-benchmark: dict.c zmalloc.c sds.c siphash.c
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
@@ -1,107 +0,0 @@
From: Chris Lamb <lamby@debian.org>
Date: Sat, 25 Aug 2018 17:52:13 +0200
Subject: Add support for USE_SYSTEM_JEMALLOC flag.
https://github.com/antirez/redis/pull/5279
---
deps/Makefile | 2 ++
src/Makefile | 5 +++++
src/object.c | 5 +++++
src/zmalloc.c | 10 ++++++++++
src/zmalloc.h | 4 ++++
5 files changed, 26 insertions(+)
diff --git a/deps/Makefile b/deps/Makefile
index eb35c1e..1342fac 100644
--- a/deps/Makefile
+++ b/deps/Makefile
@@ -36,7 +36,9 @@ distclean:
-(cd hiredis && $(MAKE) clean) > /dev/null || true
-(cd linenoise && $(MAKE) clean) > /dev/null || true
-(cd lua && $(MAKE) clean) > /dev/null || true
+ifneq ($(USE_SYSTEM_JEMALLOC),yes)
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
+endif
-(rm -f .make-*)
.PHONY: distclean
diff --git a/src/Makefile b/src/Makefile
index 0ff6e8b..51363fe 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -120,10 +120,15 @@ ifeq ($(MALLOC),tcmalloc_minimal)
endif
ifeq ($(MALLOC),jemalloc)
+ifeq ($(USE_SYSTEM_JEMALLOC),yes)
+ FINAL_CFLAGS+= -DUSE_JEMALLOC -I/usr/include/jemalloc/include
+ FINAL_LIBS := -ljemalloc $(FINAL_LIBS)
+else
DEPENDENCY_TARGETS+= jemalloc
FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS)
endif
+endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) $(CPPFLAGS)
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
diff --git a/src/object.c b/src/object.c
index 6987e1e..e49c2c2 100644
--- a/src/object.c
+++ b/src/object.c
@@ -36,6 +36,11 @@
#define strtold(a,b) ((long double)strtod((a),(b)))
#endif
+#if defined(USE_JEMALLOC) && (USE_SYSTEM_JEMALLOC == yes)
+#define je_mallctl mallctl
+#define je_malloc_stats_print malloc_stats_print
+#endif
+
/* ===================== Creation and parsing of objects ==================== */
robj *createObject(int type, void *ptr) {
diff --git a/src/zmalloc.c b/src/zmalloc.c
index 2482f51..80e6571 100644
--- a/src/zmalloc.c
+++ b/src/zmalloc.c
@@ -63,6 +63,15 @@ void zlibc_free(void *ptr) {
#define realloc(ptr,size) tc_realloc(ptr,size)
#define free(ptr) tc_free(ptr)
#elif defined(USE_JEMALLOC)
+#if USE_SYSTEM_JEMALLOC == yes
+#define malloc(size) malloc(size)
+#define calloc(count,size) calloc(count,size)
+#define realloc(ptr,size) realloc(ptr,size)
+#define free(ptr) free(ptr)
+#define mallocx(size,flags) mallocx(size,flags)
+#define dallocx(ptr,flags) dallocx(ptr,flags)
+#define je_mallctl mallctl
+#else
#define malloc(size) je_malloc(size)
#define calloc(count,size) je_calloc(count,size)
#define realloc(ptr,size) je_realloc(ptr,size)
@@ -70,6 +79,7 @@ void zlibc_free(void *ptr) {
#define mallocx(size,flags) je_mallocx(size,flags)
#define dallocx(ptr,flags) je_dallocx(ptr,flags)
#endif
+#endif
#define update_zmalloc_stat_alloc(__n) do { \
size_t _n = (__n); \
diff --git a/src/zmalloc.h b/src/zmalloc.h
index 6fb19b0..62ccf29 100644
--- a/src/zmalloc.h
+++ b/src/zmalloc.h
@@ -50,7 +50,11 @@
#include <jemalloc/jemalloc.h>
#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)
#define HAVE_MALLOC_SIZE 1
+#if USE_SYSTEM_JEMALLOC == yes
+#define zmalloc_size(p) malloc_usable_size(p)
+#else
#define zmalloc_size(p) je_malloc_usable_size(p)
+#endif
#else
#error "Newer version of jemalloc required"
#endif
-2
View File
@@ -2,7 +2,5 @@
#debian-packaging/0003-dpkg-buildflags.patch #debian-packaging/0003-dpkg-buildflags.patch
#debian-packaging/0007-Set-Debian-configuration-defaults.patch #debian-packaging/0007-Set-Debian-configuration-defaults.patch
#0010-Use-get_current_dir_name-over-PATHMAX-etc.patch #0010-Use-get_current_dir_name-over-PATHMAX-etc.patch
#0010-Add-support-for-USE_SYSTEM_JEMALLOC-flag.patch
#0011-Add-support-for-a-USE_SYSTEM_LUA-flag.patch #0011-Add-support-for-a-USE_SYSTEM_LUA-flag.patch
#0007-Add-support-for-a-USE_SYSTEM_HIREDIS-flag.patch
#test #test
+83
View File
@@ -1,3 +1,86 @@
keydb (6:6.3.4-1distribution_placeholder) codename_placeholder; urgency=medium
* Add keydb_modstatsd, a module for providing keydb stats to a local statsd server. Can be found under src/modules/keydb_modstatsd
* Fixed FreeBSD compile(Thanks to @bra-fsn)
* Fixed a bug in s3 bucket config that blocked startup(Thanks to Alexandre Chichmanian)
* Fixed a bug causing crash if keys command is called after a blocking command (Fixes issues #659, #605, #532)
* Added proper error checking of replica configs, now KeyDB will throw an error if replica-of config is passed before active-replica or multimaster configs
* Fixed double free bug in lazy free
* Added "overload-protect-percent" config, when enabled this will load shed clients whenever CPU usage exceeds configured value
* Added "availability-zone" config, this can be passed any string which will then be reported by info command
* Updated eviction logic to account for total system memory availability using sysinfo
* Fixed a bug where repl-backlog-size config was modified in keydb.conf with the runtime value during config rewrite
* Fixed a possible deadlock when running CLIENT KILL with large number of clients
* Fixed a bug where KeyDB would overcommit memory during fork BG save
* Fixed a bug with disk repl_backlog causing double free
* Fixed integer overflow issue in the temp rdb file naming (Thanks to @karthyuom)
* Fixed compile issue with GCC 13.1.1 (Thanks to @michieldwitte)
* Removed expireset and restored redis expire behaviour
* Fixed a bug causing forked processes to hang, specifically affecting RDB and AOF(Fixes issues #675, #619)
* Added "CLUSTER REPLICATE NO ONE" to turn a replica into an empty primary
* Added RDB-less full sync, can be enabled with config 'enable-keydb-fastsync'
* Added "flash-disable-key-cache" config to disable key cache which stores every key(but not value) in memory
* Moved cluster slot to key map to being stored by storage provider, rather than in memory
* Moved expires to be stored by storage provider, rather than in memory
* Enabled expiry/eviction from storage provider, previously only data stored in memory could be expired/evicted(Fixes #645 along with the 2 previous updates)
* Fixed a bug where swapdb result was not recovered after keydb restarts in FLASH mode (Thanks to @karthyuom)
* Fixed double free bug when fast sync was canceled early
* Fixed a bug where a temp rdb file with zero bytes is generated in flash mode (Thanks to @karthyuom)
* Fixed a bug where flash CF options are being reset to default after flushall (Thanks to @karthyuom)(Fixes issue #717)
* Updated debug reload command to work with flash
* Added support for KEYDB_PASSWORD env variable(Thanks to @einar-pexip)
* Compile with flash in Dockerfile(Thanks to @der-eismann)
* Fix broken redis-cli symlink (Thanks to @pimvandenbroek)
* Use tini for alpine docker(Thanks to @rofafor)
-- Malavan Sotheeswaran <msotheeswaran@snapchat.com> Sun, 29 Oct 2023 8:39:21 +0000
keydb (6:6.3.3-1distribution_placeholder) codename_placeholder; urgency=medium
* To help accelerate our development efforts for KeyDB, this will be the last release containing support for Centos 7, Ubuntu 16.04, Ubuntu 18.04, Debian 9 and 32-bit builds
* This release contains fixes for 11 issues along with improvements to the KeyDB FLASH feature:
* Fixed race condition with expireset access (Issue #597)
* Fixed keys command with lua, and added keydb as an alternative to redis as lua variable to access db (Issue #562 and #594)
* Fixed hang in aof child (Issue #554)
* Fixed leaking fds from RDB save (Issue #453 and #584)
* Added config for S3 RDB load/save (Issue #584)
* Enabled active defrag during forkless background save to improve average memory efficiency (Issue #460)
* Fixed crash with fork background save during replication (Issue #567)
* RocksDB has been updated to v7.9.2
* Fixed missing slot_to_key map in FLASH cluster mode (Issue #574)
* Added keyspace notifications for keys loaded from FLASH at startup
* Fixed race condition in prefetchKeysAsync with FLASH enabled (Issue #571)
* Fixed integer overflow in rand family of commands(Issue #631, #632, #633)
* Fixed bad value in hincrbyfloat(Issue #634)
* Fixed OOM hang in rand family of commands(Issue #635)
* Added config to limit count of return values in rand family of commands(Issue #636)
-- Ben Schermel <ben@eqalpha.com> Tue, 18 Apr 2023 10:00:37 +0000
keydb (6:6.3.2-1distribution_placeholder) codename_placeholder; urgency=medium
* This release contains Beta level support for KeyDB FLASH, new ASYNC commands, latency improvements and a number of bug fixes.
* KeyDB FLASH is included as a Beta feature. FLASH allows KeyDB to persist data to the storage medium it is written to, avoiding the need for AOF/RDB files. KeyDB uses rocksdb as the persistent storage provider and can be enabled with config "storage-provider flash /path/to/rocksdb/output". Read more at https://docs.keydb.dev/docs/flash/
* In addition to GET/MGET, ASYNC support has been added for the following commands: HGET, HMGET, HKEYS, HVALS, HGETALL, HSCAN and can be enable with config "enable-async-commands yes”
* Packaging support for Ubuntu 22.04 (Jammy) and Debian 12 (Bookworm) has been included with this release
* Added new soft shutdown feature, can be enabled with config "soft-shutdown yes".
* If soft shutdown is enabled, instead of shutting down right away, the server will wait until all clients have disconnected, and will reject all new connection attempts.
* Fixed memory leak with tls certificates when tls allowlist is enabled
* Fixed bug in rdb load with flash enabled to ensure all dbs are safe to load (previously only checked db[0])
* Fixed race conditions in rdb load and replication
* Fixed memory access of rdb file after it should have been deleted
* Fixed integer overflow bug in flash(Issue #486)
* Improve TLS latency by queueing new commands before executing instead of after
* Removed O(n) count of memory usage from info command(replaced by O(1) estimate)
* Improved latency of clearing large number of flash DBs(Thanks to Paul Chen for this fix)(Issue #516)
* replaced sprintf with snprintf to avoid potential security bugs
* Fixed bug where a failed move due to key already existing in move target would result in the key being removed from move source(Thanks to Paul Chen for this fix)(Issue #497)
* Fixed usage of deprecated OpenSSL api in OpenSSL v>3.0.1(Issue #392)
* Imported security fixes from Redis (CVE-2023-22458 CVE-2022-35977)
* Other fixed issues: #480 #477 #454 #452 #303 #425 #492 #541
-- Ben Schermel <ben@eqalpha.com> Fri, 20 Jan 2023 20:00:37 +0000
keydb (6:6.3.1-1distribution_placeholder) codename_placeholder; urgency=medium keydb (6:6.3.1-1distribution_placeholder) codename_placeholder; urgency=medium
* This point release contains fixes to bugs related to expires, active-rep, and rdb saving * This point release contains fixes to bugs related to expires, active-rep, and rdb saving
+6 -1
View File
@@ -53,7 +53,7 @@ RUN set -eux; \
grep -E '^ *createBoolConfig[(]"protected-mode",.*, *1 *,.*[)],$' ./src/config.cpp; \ grep -E '^ *createBoolConfig[(]"protected-mode",.*, *1 *,.*[)],$' ./src/config.cpp; \
sed -ri 's!^( *createBoolConfig[(]"protected-mode",.*, *)1( *,.*[)],)$!\10\2!' ./src/config.cpp; \ sed -ri 's!^( *createBoolConfig[(]"protected-mode",.*, *)1( *,.*[)],)$!\10\2!' ./src/config.cpp; \
grep -E '^ *createBoolConfig[(]"protected-mode",.*, *0 *,.*[)],$' ./src/config.cpp; \ grep -E '^ *createBoolConfig[(]"protected-mode",.*, *0 *,.*[)],$' ./src/config.cpp; \
make -j$(nproc) BUILD_TLS=yes; \ make -j$(nproc) BUILD_TLS=yes ENABLE_FLASH=yes; \
cd src; \ cd src; \
strip keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel keydb-server; \ strip keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel keydb-server; \
mv keydb-server keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel /usr/local/bin/; \ mv keydb-server keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel /usr/local/bin/; \
@@ -83,6 +83,7 @@ RUN \
sed -i 's/^\(logfile .*\)$/# \1/' /etc/keydb/keydb.conf; \ sed -i 's/^\(logfile .*\)$/# \1/' /etc/keydb/keydb.conf; \
sed -i 's/protected-mode yes/protected-mode no/g' /etc/keydb/keydb.conf; \ sed -i 's/protected-mode yes/protected-mode no/g' /etc/keydb/keydb.conf; \
sed -i 's/^\(bind .*\)$/# \1/' /etc/keydb/keydb.conf; \ sed -i 's/^\(bind .*\)$/# \1/' /etc/keydb/keydb.conf; \
cd /usr/local/bin; \
ln -s keydb-cli redis-cli; \ ln -s keydb-cli redis-cli; \
cd /etc/keydb; \ cd /etc/keydb; \
ln -s keydb.conf redis.conf; \ ln -s keydb.conf redis.conf; \
@@ -96,6 +97,10 @@ RUN set -eux; \
echo 'if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \ echo 'if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \
echo ' set -- keydb-server "$@"' >> /usr/local/bin/docker-entrypoint.sh; \ echo ' set -- keydb-server "$@"' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'fi' >> /usr/local/bin/docker-entrypoint.sh; \ echo 'fi' >> /usr/local/bin/docker-entrypoint.sh; \
echo '# if KEYDB_PASSWORD is set, add it to the arguments' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'if [ -n "$KEYDB_PASSWORD" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \
echo ' set -- "$@" --requirepass "${KEYDB_PASSWORD}"' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'fi' >> /usr/local/bin/docker-entrypoint.sh; \
echo "# allow the container to be started with `--user`" >> /usr/local/bin/docker-entrypoint.sh; \ echo "# allow the container to be started with `--user`" >> /usr/local/bin/docker-entrypoint.sh; \
echo 'if [ "$1" = "keydb-server" -a "$(id -u)" = "0" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \ echo 'if [ "$1" = "keydb-server" -a "$(id -u)" = "0" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \
echo " find . \! -user keydb -exec chown keydb '{}' +" >> /usr/local/bin/docker-entrypoint.sh; \ echo " find . \! -user keydb -exec chown keydb '{}' +" >> /usr/local/bin/docker-entrypoint.sh; \
+9 -4
View File
@@ -1,11 +1,11 @@
FROM alpine:3.12 FROM alpine:3.18
# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added # add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN addgroup -S -g 1000 keydb && adduser -S -G keydb -u 999 keydb RUN addgroup -S -g 1000 keydb && adduser -S -G keydb -u 999 keydb
RUN mkdir -p /etc/keydb RUN mkdir -p /etc/keydb
ARG BRANCH ARG BRANCH
RUN set -eux; \ RUN set -eux; \
\ \
apk add --no-cache su-exec; \ apk add --no-cache su-exec tini; \
apk add --no-cache --virtual .build-deps \ apk add --no-cache --virtual .build-deps \
coreutils \ coreutils \
gcc \ gcc \
@@ -32,7 +32,7 @@ RUN set -eux; \
grep -E '^ *createBoolConfig[(]"protected-mode",.*, *1 *,.*[)],$' ./src/config.cpp; \ grep -E '^ *createBoolConfig[(]"protected-mode",.*, *1 *,.*[)],$' ./src/config.cpp; \
sed -ri 's!^( *createBoolConfig[(]"protected-mode",.*, *)1( *,.*[)],)$!\10\2!' ./src/config.cpp; \ sed -ri 's!^( *createBoolConfig[(]"protected-mode",.*, *)1( *,.*[)],)$!\10\2!' ./src/config.cpp; \
grep -E '^ *createBoolConfig[(]"protected-mode",.*, *0 *,.*[)],$' ./src/config.cpp; \ grep -E '^ *createBoolConfig[(]"protected-mode",.*, *0 *,.*[)],$' ./src/config.cpp; \
make -j$(nproc) BUILD_TLS=yes; \ make -j$(nproc) BUILD_TLS=yes ENABLE_FLASH=yes; \
cd src; \ cd src; \
strip keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel keydb-server; \ strip keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel keydb-server; \
mv keydb-server keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel /usr/local/bin/; \ mv keydb-server keydb-cli keydb-benchmark keydb-check-rdb keydb-check-aof keydb-diagnostic-tool keydb-sentinel /usr/local/bin/; \
@@ -54,6 +54,7 @@ RUN set -eux; \
sed -i 's/^\(logfile .*\)$/# \1/' /etc/keydb/keydb.conf; \ sed -i 's/^\(logfile .*\)$/# \1/' /etc/keydb/keydb.conf; \
sed -i 's/protected-mode yes/protected-mode no/g' /etc/keydb/keydb.conf; \ sed -i 's/protected-mode yes/protected-mode no/g' /etc/keydb/keydb.conf; \
sed -i 's/^\(bind .*\)$/# \1/' /etc/keydb/keydb.conf; \ sed -i 's/^\(bind .*\)$/# \1/' /etc/keydb/keydb.conf; \
cd /usr/local/bin; \
ln -s keydb-cli redis-cli; \ ln -s keydb-cli redis-cli; \
cd /etc/keydb; \ cd /etc/keydb; \
ln -s keydb.conf redis.conf; \ ln -s keydb.conf redis.conf; \
@@ -67,6 +68,10 @@ RUN set -eux; \
echo 'if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \ echo 'if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \
echo ' set -- keydb-server "$@"' >> /usr/local/bin/docker-entrypoint.sh; \ echo ' set -- keydb-server "$@"' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'fi' >> /usr/local/bin/docker-entrypoint.sh; \ echo 'fi' >> /usr/local/bin/docker-entrypoint.sh; \
echo '# if KEYDB_PASSWORD is set, add it to the arguments' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'if [ -n "$KEYDB_PASSWORD" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \
echo ' set -- "$@" --requirepass "${KEYDB_PASSWORD}"' >> /usr/local/bin/docker-entrypoint.sh; \
echo 'fi' >> /usr/local/bin/docker-entrypoint.sh; \
echo "# allow the container to be started with `--user`" >> /usr/local/bin/docker-entrypoint.sh; \ echo "# allow the container to be started with `--user`" >> /usr/local/bin/docker-entrypoint.sh; \
echo 'if [ "$1" = "keydb-server" -a "$(id -u)" = "0" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \ echo 'if [ "$1" = "keydb-server" -a "$(id -u)" = "0" ]; then' >> /usr/local/bin/docker-entrypoint.sh; \
echo " find . \! -user keydb -exec chown keydb '{}' +" >> /usr/local/bin/docker-entrypoint.sh; \ echo " find . \! -user keydb -exec chown keydb '{}' +" >> /usr/local/bin/docker-entrypoint.sh; \
@@ -76,6 +81,6 @@ RUN set -eux; \
chmod +x /usr/local/bin/docker-entrypoint.sh chmod +x /usr/local/bin/docker-entrypoint.sh
VOLUME /data VOLUME /data
WORKDIR /data WORKDIR /data
ENTRYPOINT ["docker-entrypoint.sh"] ENTRYPOINT ["tini", "--", "docker-entrypoint.sh"]
EXPOSE 6379 EXPOSE 6379
CMD ["keydb-server", "/etc/keydb/keydb.conf"] CMD ["keydb-server", "/etc/keydb/keydb.conf"]
+3 -6
View File
@@ -5,10 +5,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
version=$(grep KEYDB_REAL_VERSION $DIR/../../src/version.h | awk '{ printf $3 }' | tr -d \") version=$(grep KEYDB_REAL_VERSION $DIR/../../src/version.h | awk '{ printf $3 }' | tr -d \")
release=1 # by default this will always be 1 for keydb version structure. If build release version needs to be update you can modify here release=1 # by default this will always be 1 for keydb version structure. If build release version needs to be update you can modify here
arch=$(uname -m) arch=$(uname -m)
dist=el$(rpm -q --queryformat '%{VERSION}' centos-release | cut -d. -f1) dist=$(rpm --eval '%{?dist}')
if [[ "$dist" == "elpackage centos-release is not installed" ]]; then
dist=el$(rpm -q --queryformat '%{VERSION}' centos-linux-release | cut -d. -f1)
fi
if [[ "$arch" != "aarch64" ]] && [[ "$arch" != "x86_64" ]]; then if [[ "$arch" != "aarch64" ]] && [[ "$arch" != "x86_64" ]]; then
echo "This script is only valid and tested for aarch64 and x86_64 architectures. You are trying to use: $arch" echo "This script is only valid and tested for aarch64 and x86_64 architectures. You are trying to use: $arch"
@@ -39,8 +36,8 @@ sed -i -E "1a\Version : $version" $DIR/keydb_build/keydb.spec
sed -i '3d' $DIR/keydb_build/keydb.spec sed -i '3d' $DIR/keydb_build/keydb.spec
sed -i -E "2a\Release : $release%{?dist}" $DIR/keydb_build/keydb.spec sed -i -E "2a\Release : $release%{?dist}" $DIR/keydb_build/keydb.spec
mkdir -p /root/rpmbuild/BUILDROOT/keydb-$version-$release.$dist.$arch mkdir -p /root/rpmbuild/BUILDROOT/keydb-$version-$release$dist.$arch
cp -r $DIR/keydb_build/keydb_rpm/* /root/rpmbuild/BUILDROOT/keydb-$version-$release.$dist.$arch/ cp -r $DIR/keydb_build/keydb_rpm/* /root/rpmbuild/BUILDROOT/keydb-$version-$release$dist.$arch/
rpmbuild -bb $DIR/keydb_build/keydb.spec rpmbuild -bb $DIR/keydb_build/keydb.spec
mv /root/rpmbuild/RPMS/$arch/* $DIR/rpm_files_generated mv /root/rpmbuild/RPMS/$arch/* $DIR/rpm_files_generated
+1
View File
@@ -37,5 +37,6 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/hash \ --single unit/moduleapi/hash \
--single unit/moduleapi/zset \ --single unit/moduleapi/zset \
--single unit/moduleapi/stream \ --single unit/moduleapi/stream \
--single unit/moduleapi/load \
--config server-threads 3 \ --config server-threads 3 \
"${@}" "${@}"
+8
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include "sds.h" #include "sds.h"
#include <string>
#define METADATA_DB_IDENTIFIER "c299fde0-6d42-4ec4-b939-34f680ffe39f" #define METADATA_DB_IDENTIFIER "c299fde0-6d42-4ec4-b939-34f680ffe39f"
@@ -14,6 +15,7 @@ public:
virtual class IStorage *createMetadataDb() = 0; virtual class IStorage *createMetadataDb() = 0;
virtual const char *name() const = 0; virtual const char *name() const = 0;
virtual size_t totalDiskspaceUsed() const = 0; virtual size_t totalDiskspaceUsed() const = 0;
virtual sdsstring getInfo() const = 0;
virtual bool FSlow() const = 0; virtual bool FSlow() const = 0;
virtual size_t filedsRequired() const { return 0; } virtual size_t filedsRequired() const { return 0; }
}; };
@@ -31,6 +33,7 @@ public:
virtual void retrieve(const char *key, size_t cchKey, callbackSingle fn) const = 0; virtual void retrieve(const char *key, size_t cchKey, callbackSingle fn) const = 0;
virtual size_t clear() = 0; virtual size_t clear() = 0;
virtual bool enumerate(callback fn) const = 0; virtual bool enumerate(callback fn) const = 0;
virtual bool enumerate_hashslot(callback fn, unsigned int hashslot) const = 0;
virtual size_t count() const = 0; virtual size_t count() const = 0;
virtual void bulkInsert(char **rgkeys, size_t *rgcbkeys, char **rgvals, size_t *rgcbvals, size_t celem) { virtual void bulkInsert(char **rgkeys, size_t *rgcbkeys, char **rgvals, size_t *rgcbvals, size_t celem) {
@@ -41,6 +44,11 @@ public:
endWriteBatch(); endWriteBatch();
} }
virtual std::vector<std::string> getExpirationCandidates(unsigned int count) = 0;
virtual std::vector<std::string> getEvictionCandidates(unsigned int count) = 0;
virtual void setExpire(const char *key, size_t cchKey, long long expire) = 0;
virtual void removeExpire(const char *key, size_t cchKey, long long expire) = 0;
virtual void beginWriteBatch() {} // NOP virtual void beginWriteBatch() {} // NOP
virtual void endWriteBatch() {} // NOP virtual void endWriteBatch() {} // NOP
+60 -12
View File
@@ -20,12 +20,12 @@ release_hdr := $(shell sh -c './mkreleasehdr.sh')
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
OPTIMIZATION?=-O2 -flto OPTIMIZATION?=-O2 -flto
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram rocksdb DEPENDENCY_TARGETS=linenoise lua hdr_histogram
NODEPS:=clean distclean NODEPS:=clean distclean
# Default settings # Default settings
STD=-pedantic -DREDIS_STATIC='' STD=-pedantic -DREDIS_STATIC=''
CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS CXX_STD=-std=c++17 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS
ifneq (,$(findstring clang,$(CC))) ifneq (,$(findstring clang,$(CC)))
STD+=-Wno-c11-extensions STD+=-Wno-c11-extensions
else else
@@ -73,12 +73,19 @@ ifneq ($(strip $(SANITIZE)),)
endif endif
ifeq ($(ENABLE_FLASH),yes) ifeq ($(ENABLE_FLASH),yes)
STORAGE_OBJ+= storage/rocksdb.o storage/rocksdbfactory.o
ifeq ($(USE_SYSTEM_ROCKSDB),yes)
FINAL_LIBS+= $(shell pkg-config --libs rocksdb)
FINAL_CXXFLAGS+= $(shell pkg-config --cflags rocksdb) -DENABLE_ROCKSDB
else
FINAL_LIBS+= -lz -lcrypto -lbz2 -lzstd -llz4 -lsnappy FINAL_LIBS+= -lz -lcrypto -lbz2 -lzstd -llz4 -lsnappy
CXXFLAGS+= -I../deps/rocksdb/include/ -DENABLE_ROCKSDB CXXFLAGS+= -I../deps/rocksdb/include/ -DENABLE_ROCKSDB
STORAGE_OBJ+= storage/rocksdb.o storage/rocksdbfactory.o FINAL_CXXFLAGS+= -I../deps/rocksdb/include/
FINAL_LIBS+= ../deps/rocksdb/librocksdb.a FINAL_LIBS+= ../deps/rocksdb/librocksdb.a
DEPENDENCY_TARGETS+= rocksdb DEPENDENCY_TARGETS+= rocksdb
endif endif
endif
ifeq ($(CHECKED),true) ifeq ($(CHECKED),true)
@@ -142,10 +149,12 @@ DEBUG=-g -ggdb
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(KEYDB_CFLAGS) $(REDIS_CFLAGS) FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(KEYDB_CFLAGS) $(REDIS_CFLAGS)
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(KEYDB_CFLAGS) $(REDIS_CFLAGS) FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(KEYDB_CFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS=$(LDFLAGS) $(KEYDB_LDFLAGS) $(DEBUG) FINAL_LDFLAGS=$(LDFLAGS) $(KEYDB_LDFLAGS) $(DEBUG)
FINAL_LIBS+=-lm -lz -lcrypto -lbz2 -lzstd -llz4 -lsnappy FINAL_LIBS+=-lm -lz -lcrypto
ifneq ($(uname_S),Darwin) ifneq ($(uname_S),Darwin)
ifneq ($(uname_S),FreeBSD)
FINAL_LIBS+=-latomic FINAL_LIBS+=-latomic
endif
endif endif
# Linux ARM32 needs -latomic at linking time # Linux ARM32 needs -latomic at linking time
ifneq (,$(findstring armv,$(uname_M))) ifneq (,$(findstring armv,$(uname_M)))
@@ -211,7 +220,13 @@ ifeq ($(uname_S),NetBSD)
else else
ifeq ($(uname_S),FreeBSD) ifeq ($(uname_S),FreeBSD)
# FreeBSD # FreeBSD
FINAL_LIBS+= -lpthread -lexecinfo FINAL_LIBS+= -lpthread -luuid -lexecinfo
FINAL_CFLAGS+= -I/usr/local/include
FINAL_CXXFLAGS+= -I/usr/local/include
FINAL_LDFLAGS+= -L/usr/local/lib
ifeq ($(USE_BACKTRACE),yes)
FINAL_CFLAGS+= -DUSE_BACKTRACE
endif
else else
ifeq ($(uname_S),DragonFly) ifeq ($(uname_S),DragonFly)
# DragonFly # DragonFly
@@ -259,8 +274,14 @@ ifdef OPENSSL_PREFIX
endif endif
# Include paths to dependencies # Include paths to dependencies
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram FINAL_CFLAGS+= -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram
FINAL_CXXFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/rocksdb/include/ -I../deps/concurrentqueue FINAL_CXXFLAGS+= -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram
ifeq ($(USE_SYSTEM_CONCURRENTQUEUE),yes)
FINAL_CXXFLAGS+= -I/usr/include/concurrentqueue/moodycamel
else
FINAL_CXXFLAGS+= -I../deps/concurrentqueue
endif
# Determine systemd support and/or build preference (defaulting to auto-detection) # Determine systemd support and/or build preference (defaulting to auto-detection)
BUILD_WITH_SYSTEMD=no BUILD_WITH_SYSTEMD=no
@@ -303,11 +324,17 @@ ifeq ($(MALLOC),tcmalloc_minimal)
endif endif
ifeq ($(MALLOC),jemalloc) ifeq ($(MALLOC),jemalloc)
ifeq ($(USE_SYSTEM_JEMALLOC),yes)
FINAL_CFLAGS+= -DUSE_JEMALLOC $(shell $(PKG_CONFIG) --cflags jemalloc)
FINAL_CXXFLAGS+= -DUSE_JEMALLOC $(shell $(PKG_CONFIG) --cflags jemalloc)
FINAL_LIBS := $(shell $(PKG_CONFIG) --libs jemalloc) $(FINAL_LIBS)
else
DEPENDENCY_TARGETS+= jemalloc DEPENDENCY_TARGETS+= jemalloc
FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
FINAL_CXXFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include FINAL_CXXFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS) FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS)
endif endif
endif
ifeq ($(MALLOC),memkind) ifeq ($(MALLOC),memkind)
DEPENDENCY_TARGETS+= memkind DEPENDENCY_TARGETS+= memkind
@@ -316,6 +343,22 @@ ifeq ($(MALLOC),memkind)
FINAL_LIBS := ../deps/memkind/src/.libs/libmemkind.a -lnuma $(FINAL_LIBS) FINAL_LIBS := ../deps/memkind/src/.libs/libmemkind.a -lnuma $(FINAL_LIBS)
endif endif
ifeq ($(USE_SYSTEM_HIREDIS),yes)
HIREDIS_CFLAGS := $(shell $(PKG_CONFIG) --cflags hiredis) -DUSE_SYSTEM_HIREDIS=1
FINAL_CFLAGS+= $(HIREDIS_CFLAGS)
FINAL_CXXFLAGS+= $(HIREDIS_CFLAGS)
FINAL_LIBS+= $(shell $(PKG_CONFIG) --libs hiredis)
ifeq ($(BUILD_TLS),yes)
HIREDIS_TLS_CFLAGS := $(shell $(PKG_CONFIG) --cflags hiredis_ssl)
FINAL_CFLAGS+= $(HIREDIS_TLS_CFLAGS)
FINAL_CXXFLAGS+= $(HIREDIS_TLS_CFLAGS)
FINAL_LIBS+= $(shell $(PKG_CONFIG) --libs hiredis_ssl)
endif
else
DEPENDENCY_TARGETS+= hiredis
FINAL_CFLAGS+= -I../deps/hiredis
FINAL_CXXFLAGS+= -I../deps/hiredis
FINAL_LIBS+=../deps/hiredis/libhiredis.a
ifeq ($(BUILD_TLS),yes) ifeq ($(BUILD_TLS),yes)
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS) FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS) FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
@@ -334,6 +377,7 @@ else
endif endif
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS) FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS)
endif endif
endif
ifndef V ifndef V
define MAKE_INSTALL define MAKE_INSTALL
@@ -350,6 +394,7 @@ endif
OS := $(shell cat /etc/os-release | grep ID= | head -n 1 | cut -d'=' -f2) OS := $(shell cat /etc/os-release | grep ID= | head -n 1 | cut -d'=' -f2)
ifeq ($(OS),alpine) ifeq ($(OS),alpine)
FINAL_CXXFLAGS+=-DUNW_LOCAL_ONLY FINAL_CXXFLAGS+=-DUNW_LOCAL_ONLY
FINAL_CXXFLAGS+=-DALPINE
FINAL_LIBS += -lunwind FINAL_LIBS += -lunwind
endif endif
@@ -376,7 +421,7 @@ endif
REDIS_SERVER_NAME=keydb-server$(PROG_SUFFIX) REDIS_SERVER_NAME=keydb-server$(PROG_SUFFIX)
REDIS_SENTINEL_NAME=keydb-sentinel$(PROG_SUFFIX) REDIS_SENTINEL_NAME=keydb-sentinel$(PROG_SUFFIX)
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 t_nhash.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 crcspeed.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 connection.o tls.o sha256.o motd_server.o timeout.o setcpuaffinity.o AsyncWorkQueue.o snapshot.o storage/rocksdb.o storage/rocksdbfactory.o storage/teststorageprovider.o keydbutils.o StorageCache.o monotonic.o cli_common.o mt19937-64.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 t_nhash.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 crcspeed.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 connection.o tls.o sha256.o motd_server.o timeout.o setcpuaffinity.o AsyncWorkQueue.o snapshot.o storage/teststorageprovider.o keydbutils.o StorageCache.o monotonic.o cli_common.o mt19937-64.o meminfo.o $(ASM_OBJ) $(STORAGE_OBJ)
KEYDB_SERVER_OBJ=SnapshotPayloadParseState.o KEYDB_SERVER_OBJ=SnapshotPayloadParseState.o
REDIS_CLI_NAME=keydb-cli$(PROG_SUFFIX) REDIS_CLI_NAME=keydb-cli$(PROG_SUFFIX)
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o motd_client.o monotonic.o cli_common.o mt19937-64.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 crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o motd_client.o monotonic.o cli_common.o mt19937-64.o $(ASM_OBJ)
@@ -405,9 +450,12 @@ persist-settings: distclean
echo STD=$(STD) >> .make-settings echo STD=$(STD) >> .make-settings
echo WARN=$(WARN) >> .make-settings echo WARN=$(WARN) >> .make-settings
echo OPT=$(OPT) >> .make-settings echo OPT=$(OPT) >> .make-settings
echo USE_SYSTEM_CONCURRENTQUEUE=$(USE_SYSTEM_CONCURRENTQUEUE) >> .make-settings
echo MALLOC=$(MALLOC) >> .make-settings echo MALLOC=$(MALLOC) >> .make-settings
echo USE_SYSTEM_JEMALLOC=$(USE_SYSTEM_JEMALLOC) >> .make-settings
echo BUILD_TLS=$(BUILD_TLS) >> .make-settings echo BUILD_TLS=$(BUILD_TLS) >> .make-settings
echo USE_SYSTEMD=$(USE_SYSTEMD) >> .make-settings echo USE_SYSTEMD=$(USE_SYSTEMD) >> .make-settings
echo USE_SYSTEM_HIREDIS=$(USE_SYSTEM_HIREDIS) >> .make-settings
echo CFLAGS=$(CFLAGS) >> .make-settings echo CFLAGS=$(CFLAGS) >> .make-settings
echo CXXFLAGS=$(CXXFLAGS) >> .make-settings echo CXXFLAGS=$(CXXFLAGS) >> .make-settings
echo LDFLAGS=$(LDFLAGS) >> .make-settings echo LDFLAGS=$(LDFLAGS) >> .make-settings
@@ -437,7 +485,7 @@ endif
# keydb-server # keydb-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) $(KEYDB_SERVER_OBJ) $(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) $(KEYDB_SERVER_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/rocksdb/librocksdb.a $(FINAL_LIBS) $(REDIS_LD) -o $@ $^ ../deps/lua/src/liblua.a $(FINAL_LIBS)
# keydb-sentinel # keydb-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
@@ -453,15 +501,15 @@ $(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME)
# keydb-cli # keydb-cli
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ) $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS) $(REDIS_LD) -o $@ $^ ../deps/linenoise/linenoise.o $(FINAL_LIBS)
# keydb-benchmark # keydb-benchmark
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ) $(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS) $(REDIS_LD) -o $@ $^ ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)
# keydb-diagnostic-tool # keydb-diagnostic-tool
$(KEYDB_DIAGNOSTIC_NAME): $(KEYDB_DIAGNOSTIC_OBJ) $(KEYDB_DIAGNOSTIC_NAME): $(KEYDB_DIAGNOSTIC_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) $(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(KEYDB_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d) DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(KEYDB_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
-include $(DEP) -include $(DEP)
+28 -9
View File
@@ -136,14 +136,33 @@ void SnapshotPayloadParseState::flushQueuedKeys() {
int idb = current_database; int idb = current_database;
serverAssert(vecqueuedKeys.size() == vecqueuedVals.size()); serverAssert(vecqueuedKeys.size() == vecqueuedVals.size());
auto sizePrev = vecqueuedKeys.size(); auto sizePrev = vecqueuedKeys.size();
++insertsInFlight;
auto &insertsInFlightTmp = insertsInFlight; // C++ GRRRRRRRRRRRRRRRR, we don't want to capute "this" because that's dangerous
if (current_database < cserver.dbnum) { if (current_database < cserver.dbnum) {
g_pserver->asyncworkqueue->AddWorkFunction([idb, vecqueuedKeys = std::move(this->vecqueuedKeys), vecqueuedKeysCb = std::move(this->vecqueuedKeysCb), vecqueuedVals = std::move(this->vecqueuedVals), vecqueuedValsCb = std::move(this->vecqueuedValsCb), &insertsInFlightTmp, pallocator = m_spallocator.release()]() mutable { if (g_pserver->m_pstorageFactory) {
g_pserver->db[idb]->bulkStorageInsert(vecqueuedKeys.data(), vecqueuedKeysCb.data(), vecqueuedVals.data(), vecqueuedValsCb.data(), vecqueuedKeys.size()); (*insertsInFlight)++;
--insertsInFlightTmp; std::weak_ptr<std::atomic<int>> insertsInFlightTmp = insertsInFlight; // C++ GRRRRRRRRRRRRRRRR, we don't want to capute "this" because that's dangerous
delete pallocator; g_pserver->asyncworkqueue->AddWorkFunction([idb, vecqueuedKeys = std::move(this->vecqueuedKeys), vecqueuedKeysCb = std::move(this->vecqueuedKeysCb), vecqueuedVals = std::move(this->vecqueuedVals), vecqueuedValsCb = std::move(this->vecqueuedValsCb), insertsInFlightTmp, pallocator = m_spallocator.release()]() mutable {
}); g_pserver->db[idb]->bulkDirectStorageInsert(vecqueuedKeys.data(), vecqueuedKeysCb.data(), vecqueuedVals.data(), vecqueuedValsCb.data(), vecqueuedKeys.size());
(*(insertsInFlightTmp.lock()))--;
delete pallocator;
});
} else {
for (size_t ival = 0; ival < vecqueuedKeys.size(); ++ival) {
size_t offset = 0;
auto spexpire = deserializeExpire(vecqueuedVals[ival], vecqueuedValsCb[ival], &offset);
auto o = deserializeStoredObject(vecqueuedVals[ival] + offset, vecqueuedValsCb[ival] - offset);
sds sdsKey = sdsnewlen(vecqueuedKeys[ival], -static_cast<ssize_t>(vecqueuedKeysCb[ival]));
if (dbMerge(g_pserver->db[idb], sdsKey, o, false)) {
if (spexpire != nullptr)
g_pserver->db[idb]->setExpire(sdsKey, std::move(*spexpire));
} else {
sdsfree(sdsKey);
}
}
vecqueuedKeys.clear();
vecqueuedKeysCb.clear();
vecqueuedVals.clear();
vecqueuedValsCb.clear();
}
} else { } else {
// else drop the data // else drop the data
vecqueuedKeys.clear(); vecqueuedKeys.clear();
@@ -172,7 +191,7 @@ SnapshotPayloadParseState::SnapshotPayloadParseState() {
dictLongLongMetaData = dictCreate(&metadataLongLongDictType, nullptr); dictLongLongMetaData = dictCreate(&metadataLongLongDictType, nullptr);
dictMetaData = dictCreate(&metadataDictType, nullptr); dictMetaData = dictCreate(&metadataDictType, nullptr);
insertsInFlight = 0; insertsInFlight = std::make_shared<std::atomic<int>>();
m_spallocator = std::make_unique<SlabAllocator>(); m_spallocator = std::make_unique<SlabAllocator>();
} }
@@ -214,7 +233,7 @@ void SnapshotPayloadParseState::trimState() {
if (stackParse.empty()) { if (stackParse.empty()) {
flushQueuedKeys(); flushQueuedKeys();
while (insertsInFlight > 0) { while (*insertsInFlight > 0) {
// TODO: ProcessEventsWhileBlocked // TODO: ProcessEventsWhileBlocked
aeReleaseLock(); aeReleaseLock();
aeAcquireLock(); aeAcquireLock();
+2 -2
View File
@@ -36,7 +36,7 @@ class SnapshotPayloadParseState {
std::vector<size_t> vecqueuedValsCb; std::vector<size_t> vecqueuedValsCb;
std::atomic<int> insertsInFlight; std::shared_ptr<std::atomic<int>> insertsInFlight;
std::unique_ptr<SlabAllocator> m_spallocator; std::unique_ptr<SlabAllocator> m_spallocator;
dict *dictLongLongMetaData = nullptr; dict *dictLongLongMetaData = nullptr;
dict *dictMetaData = nullptr; dict *dictMetaData = nullptr;
@@ -62,5 +62,5 @@ public:
void pushArray(long long size); void pushArray(long long size);
void pushValue(const char *rgch, long long cch); void pushValue(const char *rgch, long long cch);
void pushValue(long long value); void pushValue(long long value);
bool shouldThrottle() const { return insertsInFlight > (cserver.cthreads*4); } bool shouldThrottle() const { return *insertsInFlight > (cserver.cthreads*4); }
}; };
+32 -12
View File
@@ -21,7 +21,7 @@ dictType dbStorageCacheType = {
StorageCache::StorageCache(IStorage *storage, bool fCache) StorageCache::StorageCache(IStorage *storage, bool fCache)
: m_spstorage(storage) : m_spstorage(storage)
{ {
if (fCache) if (!g_pserver->flash_disable_key_cache && fCache)
m_pdict = dictCreate(&dbStorageCacheType, nullptr); m_pdict = dictCreate(&dbStorageCacheType, nullptr);
} }
@@ -84,19 +84,31 @@ void StorageCache::cacheKey(const char *rgch, size_t cch)
bool StorageCache::erase(sds key) bool StorageCache::erase(sds key)
{ {
unsigned long long when = 0;
m_spstorage->retrieve(key, sdslen(key), [&when](const char *, size_t, const void * data, size_t cbdata) {
auto e = deserializeExpire((const char *)data, cbdata, nullptr);
if (e != nullptr)
when = e->when();
});
bool result = m_spstorage->erase(key, sdslen(key)); bool result = m_spstorage->erase(key, sdslen(key));
std::unique_lock<fastlock> ul(m_lock); std::unique_lock<fastlock> ul(m_lock);
if (result && m_pdict != nullptr) if (result)
{ {
uint64_t hash = dictSdsHash(key); if (m_pdict != nullptr)
dictEntry *de = dictFind(m_pdict, reinterpret_cast<void*>(hash)); {
serverAssert(de != nullptr); uint64_t hash = dictSdsHash(key);
de->v.s64--; dictEntry *de = dictFind(m_pdict, reinterpret_cast<void*>(hash));
serverAssert(de->v.s64 >= 0); serverAssert(de != nullptr);
if (de->v.s64 == 0) { de->v.s64--;
dictDelete(m_pdict, reinterpret_cast<void*>(hash)); serverAssert(de->v.s64 >= 0);
} else { if (de->v.s64 == 0) {
m_collisionCount--; dictDelete(m_pdict, reinterpret_cast<void*>(hash));
} else {
m_collisionCount--;
}
}
if (when != 0) {
m_spstorage->removeExpire(key, sdslen(key), when);
} }
} }
return result; return result;
@@ -111,6 +123,9 @@ void StorageCache::insert(sds key, const void *data, size_t cbdata, bool fOverwr
} }
ul.unlock(); ul.unlock();
m_spstorage->insert(key, sdslen(key), (void*)data, cbdata, fOverwrite); m_spstorage->insert(key, sdslen(key), (void*)data, cbdata, fOverwrite);
auto e = deserializeExpire((const char *)data, cbdata, nullptr);
if (e != nullptr)
m_spstorage->setExpire(key, sdslen(key), e->when());
} }
long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing); long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing);
@@ -119,13 +134,18 @@ void StorageCache::bulkInsert(char **rgkeys, size_t *rgcbkeys, char **rgvals, si
std::vector<dictEntry*> vechashes; std::vector<dictEntry*> vechashes;
if (m_pdict != nullptr) { if (m_pdict != nullptr) {
vechashes.reserve(celem); vechashes.reserve(celem);
}
for (size_t ielem = 0; ielem < celem; ++ielem) { for (size_t ielem = 0; ielem < celem; ++ielem) {
if (m_pdict != nullptr) {
dictEntry *de = (dictEntry*)zmalloc(sizeof(dictEntry)); dictEntry *de = (dictEntry*)zmalloc(sizeof(dictEntry));
de->key = (void*)dictGenHashFunction(rgkeys[ielem], (int)rgcbkeys[ielem]); de->key = (void*)dictGenHashFunction(rgkeys[ielem], (int)rgcbkeys[ielem]);
de->v.u64 = 1; de->v.u64 = 1;
vechashes.push_back(de); vechashes.push_back(de);
} }
auto e = deserializeExpire(rgvals[ielem], rgcbvals[ielem], nullptr);
if (e != nullptr)
m_spstorage->setExpire(rgkeys[ielem], rgcbkeys[ielem], e->when());
} }
std::unique_lock<fastlock> ul(m_lock); std::unique_lock<fastlock> ul(m_lock);
+8
View File
@@ -49,12 +49,20 @@ public:
void expand(uint64_t slots); void expand(uint64_t slots);
bool enumerate(IStorage::callback fn) const { return m_spstorage->enumerate(fn); } bool enumerate(IStorage::callback fn) const { return m_spstorage->enumerate(fn); }
bool enumerate_hashslot(IStorage::callback fn, unsigned int hashslot) const { return m_spstorage->enumerate_hashslot(fn, hashslot); }
std::vector<std::string> getExpirationCandidates(unsigned int count) { return m_spstorage->getExpirationCandidates(count); }
std::vector<std::string> getEvictionCandidates(unsigned int count) { return m_spstorage->getEvictionCandidates(count); }
void setExpire(const char *key, size_t cchKey, long long expire) { m_spstorage->setExpire(key, cchKey, expire); }
void removeExpire(const char *key, size_t cchKey, long long expire) { m_spstorage->removeExpire(key, cchKey, expire); }
void beginWriteBatch(); void beginWriteBatch();
void endWriteBatch() { m_spstorage->endWriteBatch(); } void endWriteBatch() { m_spstorage->endWriteBatch(); }
void batch_lock() { return m_spstorage->batch_lock(); } void batch_lock() { return m_spstorage->batch_lock(); }
void batch_unlock() { return m_spstorage->batch_unlock(); } void batch_unlock() { return m_spstorage->batch_unlock(); }
void flush() { m_spstorage->flush(); }
size_t count() const; size_t count() const;
const StorageCache *clone(); const StorageCache *clone();
+39 -5
View File
@@ -113,6 +113,8 @@ enum class AE_ASYNC_OP
PostCppFunction, PostCppFunction,
DeleteFileEvent, DeleteFileEvent,
CreateFileEvent, CreateFileEvent,
PostAsynDBFunction,
}; };
struct aeCommand struct aeCommand
@@ -125,6 +127,8 @@ struct aeCommand
aePostFunctionProc *proc; aePostFunctionProc *proc;
aeFileProc *fproc; aeFileProc *fproc;
std::function<void()> *pfn; std::function<void()> *pfn;
aePostFunctionTokenProc* tproc;
}; };
void *clientData; void *clientData;
}; };
@@ -175,6 +179,17 @@ void aeProcessCmd(aeEventLoop *eventLoop, int fd, void *, int )
(*cmd.pfn)(); (*cmd.pfn)();
delete cmd.pfn; delete cmd.pfn;
break;
}
case AE_ASYNC_OP::PostAsynDBFunction:
{ //added to support async api IStorage
if (cmd.fLock && !ulock.owns_lock()) {
g_forkLock.releaseRead();
ulock.lock();
g_forkLock.acquireRead();
}
((aePostFunctionTokenProc*)cmd.tproc)(eventLoop,(StorageToken*)cmd.clientData);
break;
} }
break; break;
} }
@@ -256,6 +271,20 @@ int aePostFunction(aeEventLoop *eventLoop, aePostFunctionProc *proc, void *arg)
return AE_OK; return AE_OK;
} }
int aePostFunction(aeEventLoop *eventLoop, aePostFunctionTokenProc *proc, StorageToken *token)
{
//added to support async api IStorage
aeCommand cmd = {};
cmd.op = AE_ASYNC_OP::PostAsynDBFunction;
cmd.tproc = proc;
cmd.clientData = (void*)token;
cmd.fLock = true;
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
if (size != sizeof(cmd))
return AE_ERR;
return AE_OK;
}
int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fLock, bool fForceQueue) int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fLock, bool fForceQueue)
{ {
if (eventLoop == g_eventLoopThisThread && !fForceQueue) if (eventLoop == g_eventLoopThisThread && !fForceQueue)
@@ -716,11 +745,6 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
} }
} }
if (eventLoop->flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
}
if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP) { if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP) {
std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock); std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock);
if (!(eventLoop->beforesleepFlags & AE_SLEEP_THREADSAFE)) { if (!(eventLoop->beforesleepFlags & AE_SLEEP_THREADSAFE)) {
@@ -731,6 +755,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
eventLoop->beforesleep(eventLoop); eventLoop->beforesleep(eventLoop);
} }
if (eventLoop->flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
}
/* Call the multiplexing API, will return only on timeout or when /* Call the multiplexing API, will return only on timeout or when
* some event fires. */ * some event fires. */
numevents = aeApiPoll(eventLoop, tvp); numevents = aeApiPoll(eventLoop, tvp);
@@ -859,6 +888,11 @@ void aeReleaseForkLock()
g_forkLock.downgradeWrite(); g_forkLock.downgradeWrite();
} }
void aeForkLockInChild()
{
g_forkLock.setMulti(false);
}
int aeThreadOwnsLock() int aeThreadOwnsLock()
{ {
return fOwnLockOverride || g_lock.fOwnLock(); return fOwnLockOverride || g_lock.fOwnLock();
+6 -1
View File
@@ -72,13 +72,15 @@ extern "C" {
#define AE_NOTUSED(V) ((void) V) #define AE_NOTUSED(V) ((void) V)
struct aeEventLoop; struct aeEventLoop;
/* Types and data structures */ /* Types and data structures */
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask); typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData); typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData); typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop); typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop);
typedef void aePostFunctionProc(void *pvArgs); typedef void aePostFunctionProc(void *pvArgs);
//added to support async api IStorage
struct StorageToken;
typedef void aePostFunctionTokenProc(struct aeEventLoop *el, struct StorageToken *token);
/* File event structure */ /* File event structure */
typedef struct aeFileEvent { typedef struct aeFileEvent {
@@ -134,6 +136,8 @@ int aePostFunction(aeEventLoop *eventLoop, aePostFunctionProc *proc, void *arg);
#ifdef __cplusplus #ifdef __cplusplus
} // EXTERN C } // EXTERN C
int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fLock = true, bool fForceQueue = false); int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fLock = true, bool fForceQueue = false);
//added to support async api IStorage
int aePostFunction(aeEventLoop *eventLoop, aePostFunctionTokenProc *proc, StorageToken *token);
extern "C" { extern "C" {
#endif #endif
void aeDeleteEventLoop(aeEventLoop *eventLoop); void aeDeleteEventLoop(aeEventLoop *eventLoop);
@@ -171,6 +175,7 @@ int aeTryAcquireLock(int fWeak);
void aeThreadOffline(); void aeThreadOffline();
void aeReleaseLock(); void aeReleaseLock();
void aeReleaseForkLock(); void aeReleaseForkLock();
void aeForkLockInChild();
int aeThreadOwnsLock(); int aeThreadOwnsLock();
void aeSetThreadOwnsLockOverride(int fOverride); void aeSetThreadOwnsLockOverride(int fOverride);
int aeLockContested(int threshold); int aeLockContested(int threshold);
+1 -1
View File
@@ -441,7 +441,7 @@ static int _anetTcpServer(char *err, int port, const char *bindaddr, int af, int
char _port[6]; /* strlen("65535") */ char _port[6]; /* strlen("65535") */
struct addrinfo hints, *servinfo, *p; struct addrinfo hints, *servinfo, *p;
snprintf(_port,6,"%d",port); snprintf(_port,sizeof(_port),"%d",port);
memset(&hints,0,sizeof(hints)); memset(&hints,0,sizeof(hints));
hints.ai_family = af; hints.ai_family = af;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
+12 -13
View File
@@ -285,7 +285,7 @@ int startAppendOnly(void) {
strerror(errno)); strerror(errno));
return C_ERR; return C_ERR;
} }
if (hasActiveChildProcess() && g_pserver->child_type != CHILD_TYPE_AOF) { if (hasActiveChildProcessOrBGSave() && g_pserver->child_type != CHILD_TYPE_AOF) {
g_pserver->aof_rewrite_scheduled = 1; g_pserver->aof_rewrite_scheduled = 1;
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible."); serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
} else { } else {
@@ -438,7 +438,7 @@ void flushAppendOnlyFile(int force) {
* useful for graphing / monitoring purposes. */ * useful for graphing / monitoring purposes. */
if (sync_in_progress) { if (sync_in_progress) {
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency); latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
} else if (hasActiveChildProcess()) { } else if (hasActiveChildProcessOrBGSave()) {
latencyAddSampleIfNeeded("aof-write-active-child",latency); latencyAddSampleIfNeeded("aof-write-active-child",latency);
} else { } else {
latencyAddSampleIfNeeded("aof-write-alone",latency); latencyAddSampleIfNeeded("aof-write-alone",latency);
@@ -535,7 +535,7 @@ void flushAppendOnlyFile(int force) {
try_fsync: try_fsync:
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
* children doing I/O in the background. */ * children doing I/O in the background. */
if (g_pserver->aof_no_fsync_on_rewrite && hasActiveChildProcess()) if (g_pserver->aof_no_fsync_on_rewrite && hasActiveChildProcessOrBGSave())
return; return;
/* Perform the fsync if needed. */ /* Perform the fsync if needed. */
@@ -752,7 +752,7 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
* accumulate the differences between the child DB and the current one * accumulate the differences between the child DB and the current one
* in a buffer, so that when the child process will do its work we * in a buffer, so that when the child process will do its work we
* can append the differences to the new append only file. */ * can append the differences to the new append only file. */
if (g_pserver->child_type == CHILD_TYPE_AOF) if (hasActiveChildProcess() && g_pserver->child_type == CHILD_TYPE_AOF)
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf)); aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
sdsfree(buf); sdsfree(buf);
@@ -1592,8 +1592,7 @@ int rewriteAppendOnlyFileRio(rio *aof) {
} }
/* Save the expire time */ /* Save the expire time */
if (o->FExpires()) { if (o->FExpires()) {
std::unique_lock<fastlock> ul(g_expireLock); expireEntry *pexpire = &o->expire;
expireEntry *pexpire = db->getExpire(&key);
for (auto &subExpire : *pexpire) { for (auto &subExpire : *pexpire) {
if (subExpire.subkey() == nullptr) if (subExpire.subkey() == nullptr)
{ {
@@ -1658,7 +1657,7 @@ int rewriteAppendOnlyFile(char *filename) {
{ // BEGIN GOTO SCOPED VARIABLES { // BEGIN GOTO SCOPED VARIABLES
/* Note that we have to use a different temp name here compared to the /* Note that we have to use a different temp name here compared to the
* one used by rewriteAppendOnlyFileBackground() function. */ * one used by rewriteAppendOnlyFileBackground() function. */
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid()); snprintf(tmpfile,sizeof(tmpfile),"temp-rewriteaof-%d.aof", (int) getpid());
fp = fopen(tmpfile,"w"); fp = fopen(tmpfile,"w");
if (!fp) { if (!fp) {
serverLog(LL_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno)); serverLog(LL_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno));
@@ -1887,7 +1886,7 @@ void aofClosePipes(void) {
int rewriteAppendOnlyFileBackground(void) { int rewriteAppendOnlyFileBackground(void) {
pid_t childpid; pid_t childpid;
if (hasActiveChildProcess()) return C_ERR; if (hasActiveChildProcessOrBGSave()) return C_ERR;
if (aofCreatePipes() != C_OK) return C_ERR; if (aofCreatePipes() != C_OK) return C_ERR;
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) { if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
char tmpfile[256]; char tmpfile[256];
@@ -1895,7 +1894,7 @@ int rewriteAppendOnlyFileBackground(void) {
/* Child */ /* Child */
redisSetProcTitle("keydb-aof-rewrite"); redisSetProcTitle("keydb-aof-rewrite");
redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist); redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist);
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); snprintf(tmpfile,sizeof(tmpfile),"temp-rewriteaof-bg-%d.aof", (int) getpid());
if (rewriteAppendOnlyFile(tmpfile) == C_OK) { if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite"); sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");
exitFromChild(0); exitFromChild(0);
@@ -1930,7 +1929,7 @@ int rewriteAppendOnlyFileBackground(void) {
void bgrewriteaofCommand(client *c) { void bgrewriteaofCommand(client *c) {
if (g_pserver->child_type == CHILD_TYPE_AOF) { if (g_pserver->child_type == CHILD_TYPE_AOF) {
addReplyError(c,"Background append only file rewriting already in progress"); addReplyError(c,"Background append only file rewriting already in progress");
} else if (hasActiveChildProcess()) { } else if (hasActiveChildProcessOrBGSave()) {
g_pserver->aof_rewrite_scheduled = 1; g_pserver->aof_rewrite_scheduled = 1;
addReplyStatus(c,"Background append only file rewriting scheduled"); addReplyStatus(c,"Background append only file rewriting scheduled");
} else if (rewriteAppendOnlyFileBackground() == C_OK) { } else if (rewriteAppendOnlyFileBackground() == C_OK) {
@@ -1944,10 +1943,10 @@ void bgrewriteaofCommand(client *c) {
void aofRemoveTempFile(pid_t childpid) { void aofRemoveTempFile(pid_t childpid) {
char tmpfile[256]; char tmpfile[256];
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid); snprintf(tmpfile,sizeof(tmpfile),"temp-rewriteaof-bg-%d.aof", (int) childpid);
bg_unlink(tmpfile); bg_unlink(tmpfile);
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) childpid); snprintf(tmpfile,sizeof(tmpfile),"temp-rewriteaof-%d.aof", (int) childpid);
bg_unlink(tmpfile); bg_unlink(tmpfile);
} }
@@ -1985,7 +1984,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
/* Flush the differences accumulated by the parent to the /* Flush the differences accumulated by the parent to the
* rewritten AOF. */ * rewritten AOF. */
latencyStartMonitor(latency); latencyStartMonitor(latency);
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", snprintf(tmpfile,sizeof(tmpfile),"temp-rewriteaof-bg-%d.aof",
(int)g_pserver->child_pid); (int)g_pserver->child_pid);
newfd = open(tmpfile,O_WRONLY|O_APPEND); newfd = open(tmpfile,O_WRONLY|O_APPEND);
if (newfd == -1) { if (newfd == -1) {
+1 -1
View File
@@ -322,7 +322,7 @@ handle_wrap:
return 1; return 1;
} }
int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int owtype, int64_t *limit) { int checkSignedBitfieldOverflow(int64_t value, int64_t incr, int bits, int owtype, int64_t *limit) {
int64_t max = (bits == 64) ? INT64_MAX : (((int64_t)1<<(bits-1))-1); int64_t max = (bits == 64) ? INT64_MAX : (((int64_t)1<<(bits-1))-1);
int64_t min = (-max)-1; int64_t min = (-max)-1;
+5 -2
View File
@@ -208,7 +208,8 @@ void unblockClient(client *c) {
listDelNode(g_pserver->paused_clients,c->paused_list_node); listDelNode(g_pserver->paused_clients,c->paused_list_node);
c->paused_list_node = NULL; c->paused_list_node = NULL;
} else { } else {
serverPanic("Unknown btype in unblockClient()."); serverPanic("Unknown btype %d in unblockClient() for client %llu.",
c->btype, (unsigned long long) c->id);
} }
/* Reset the client for a new query since, for blocking commands /* Reset the client for a new query since, for blocking commands
@@ -243,7 +244,8 @@ void replyToBlockedClientTimedOut(client *c) {
} else if (c->btype == BLOCKED_MODULE) { } else if (c->btype == BLOCKED_MODULE) {
moduleBlockedClientTimedOut(c); moduleBlockedClientTimedOut(c);
} else { } else {
serverPanic("Unknown btype in replyToBlockedClientTimedOut()."); serverPanic("Unknown btype %d in replyToBlockedClientTimedOut() for client %llu.",
c->btype, (unsigned long long) c->id);
} }
} }
@@ -732,6 +734,7 @@ void unblockClientWaitingData(client *c) {
c->bpop.xread_group = NULL; c->bpop.xread_group = NULL;
c->bpop.xread_consumer = NULL; c->bpop.xread_consumer = NULL;
} }
c->bpop.timeout = 0;
} }
static int getBlockedTypeByType(int type) { static int getBlockedTypeByType(int type) {
+1
View File
@@ -42,6 +42,7 @@ typedef struct {
* RDB / AOF saving process from the child to the parent (for instance * RDB / AOF saving process from the child to the parent (for instance
* the amount of copy on write memory used) */ * the amount of copy on write memory used) */
void openChildInfoPipe(void) { void openChildInfoPipe(void) {
serverAssert(g_pserver->child_info_pipe[0] == -1);
if (pipe(g_pserver->child_info_pipe) == -1) { if (pipe(g_pserver->child_info_pipe) == -1) {
/* On error our two file descriptors should be still set to -1, /* On error our two file descriptors should be still set to -1,
* but we call anyway closeChildInfoPipe() since can't hurt. */ * but we call anyway closeChildInfoPipe() since can't hurt. */
+1 -1
View File
@@ -31,7 +31,7 @@
#include "cli_common.h" #include "cli_common.h"
#include <errno.h> #include <errno.h>
#include <hiredis.h> #include <hiredis.h>
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */ #include "sdscompat.h" /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ #include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
#include <openssl/ssl.h> #include <openssl/ssl.h>
+43 -16
View File
@@ -4516,8 +4516,8 @@ void clusterCommand(client *c) {
"NODES", "NODES",
" Return cluster configuration seen by node. Output format:", " Return cluster configuration seen by node. Output format:",
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...", " <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...",
"REPLICATE <node-id>", "REPLICATE (<node-id>|NO ONE)",
" Configure current node as replica to <node-id>.", " Configure current node as replica to <node-id> or turn it into empty primary.",
"RESET [HARD|SOFT]", "RESET [HARD|SOFT]",
" Reset current node (default: soft).", " Reset current node (default: soft).",
"SET-CONFIG-EPOCH <epoch>", "SET-CONFIG-EPOCH <epoch>",
@@ -4890,14 +4890,22 @@ NULL
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE| clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|
CLUSTER_TODO_SAVE_CONFIG); CLUSTER_TODO_SAVE_CONFIG);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"replicate") && c->argc == 3) { } else if (!strcasecmp(szFromObj(c->argv[1]),"replicate") && (c->argc == 3 || c->argc == 4)) {
/* CLUSTER REPLICATE <NODE ID> */ /* CLUSTER REPLICATE (<NODE ID>|NO ONE) */
clusterNode *n = clusterLookupNode(szFromObj(c->argv[2])); clusterNode *n;
if (c->argc == 4) {
/* Lookup the specified node in our table. */ if (0 != strcasecmp(szFromObj(c->argv[2]),"NO") || 0 != strcasecmp(szFromObj(c->argv[3]),"ONE")) {
if (!n) { addReplySubcommandSyntaxError(c);
addReplyErrorFormat(c,"Unknown node %s", (char*)ptrFromObj(c->argv[2])); return;
return; }
n = nullptr;
} else {
/* Lookup the specified node in our table. */
n = clusterLookupNode(szFromObj(c->argv[2]));
if (n == nullptr) {
addReplyErrorFormat(c,"Unknown node %s", (char*)ptrFromObj(c->argv[2]));
return;
}
} }
/* I can't replicate myself. */ /* I can't replicate myself. */
@@ -4907,7 +4915,7 @@ NULL
} }
/* Can't replicate a slave. */ /* Can't replicate a slave. */
if (nodeIsSlave(n)) { if (n != nullptr && nodeIsSlave(n)) {
addReplyError(c,"I can only replicate a master, not a replica."); addReplyError(c,"I can only replicate a master, not a replica.");
return; return;
} }
@@ -4923,8 +4931,26 @@ NULL
return; return;
} }
/* Set the master. */ if (n == nullptr) {
clusterSetMaster(n); if (nodeIsMaster(myself)) {
addReply(c,shared.ok);
return;
}
serverLog(LL_NOTICE,"Stop replication and turning myself into empty primary.");
clusterSetNodeAsMaster(myself);
if (listLength(g_pserver->masters) > 0)
{
serverAssert(listLength(g_pserver->masters) == 1);
replicationUnsetMaster((redisMaster*)listFirst(g_pserver->masters)->value);
}
int empty_db_flags = g_pserver->repl_slave_lazy_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS;
emptyDb(-1,empty_db_flags, nullptr);
/* Reset manual failover state. */
resetManualFailover();
} else {
/* Set the master. */
clusterSetMaster(n);
}
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if ((!strcasecmp(szFromObj(c->argv[1]),"slaves") || } else if ((!strcasecmp(szFromObj(c->argv[1]),"slaves") ||
@@ -5107,7 +5133,7 @@ void createDumpPayload(rio *payload, robj_roptr o, robj *key) {
serverAssert(rdbSaveObject(payload,o,key)); serverAssert(rdbSaveObject(payload,o,key));
char szT[32]; char szT[32];
uint64_t mvcc = mvccFromObj(o); uint64_t mvcc = mvccFromObj(o);
snprintf(szT, 32, "%" PRIu64, mvcc); snprintf(szT, sizeof(szT), "%" PRIu64, mvcc);
serverAssert(rdbSaveAuxFieldStrStr(payload,"mvcc-tstamp", szT) != -1); serverAssert(rdbSaveAuxFieldStrStr(payload,"mvcc-tstamp", szT) != -1);
/* Write the footer, this is how it looks like: /* Write the footer, this is how it looks like:
@@ -5584,7 +5610,6 @@ try_again:
/* Create RESTORE payload and generate the protocol to call the command. */ /* Create RESTORE payload and generate the protocol to call the command. */
for (j = 0; j < num_keys; j++) { for (j = 0; j < num_keys; j++) {
long long ttl = 0; long long ttl = 0;
std::unique_lock<fastlock> ul(g_expireLock);
expireEntry *pexpire = c->db->getExpire(kv[j]); expireEntry *pexpire = c->db->getExpire(kv[j]);
long long expireat = INVALID_EXPIRE; long long expireat = INVALID_EXPIRE;
if (pexpire != nullptr) if (pexpire != nullptr)
@@ -5966,7 +5991,9 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
/* If it is not the first key, make sure it is exactly /* If it is not the first key, make sure it is exactly
* the same key as the first we saw. */ * the same key as the first we saw. */
if (!equalStringObjects(firstkey,thiskey)) { if (!equalStringObjects(firstkey,thiskey)) {
if (slot != thisslot) { clusterNode* nThisKey = g_pserver->cluster->slots[thisslot];
if ((slot != thisslot) && (nThisKey != n || migrating_slot || importing_slot || g_pserver->cluster->migrating_slots_to[slot] != nullptr || g_pserver->cluster->importing_slots_from[slot] != nullptr)) {
/* Error: multiple keys from different slots. */ /* Error: multiple keys from different slots. */
getKeysFreeResult(&result); getKeysFreeResult(&result);
if (error_code) if (error_code)
+82 -8
View File
@@ -35,11 +35,13 @@
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h>
#ifdef __linux__ #ifdef __linux__
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#endif #endif
const char *KEYDB_SET_VERSION = KEYDB_REAL_VERSION; const char *KEYDB_SET_VERSION = KEYDB_REAL_VERSION;
size_t g_semiOrderedSetTargetBucketSize = 0; // Its a header only class so nowhere else for this to go
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Config file name-value maps. * Config file name-value maps.
@@ -747,6 +749,18 @@ void loadServerConfigFromString(char *config) {
g_pserver->fActiveReplica = CONFIG_DEFAULT_ACTIVE_REPLICA; g_pserver->fActiveReplica = CONFIG_DEFAULT_ACTIVE_REPLICA;
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
if (listLength(g_pserver->masters) && g_pserver->fActiveReplica) {
err = "must not set replica-of config before active-replica config"; goto loaderr;
}
} else if (!strcasecmp(argv[0], "multi-master") && argc == 2) {
g_pserver->enable_multimaster = yesnotoi(argv[1]);
if (g_pserver->enable_multimaster == -1) {
g_pserver->enable_multimaster = CONFIG_DEFAULT_ENABLE_MULTIMASTER;
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
if (listLength(g_pserver->masters) && g_pserver->enable_multimaster) {
err = "must not set replica-of config before multi-master config"; goto loaderr;
}
} else if (!strcasecmp(argv[0], "tls-allowlist")) { } else if (!strcasecmp(argv[0], "tls-allowlist")) {
if (argc < 2) { if (argc < 2) {
err = "must supply at least one element in the allow list"; goto loaderr; err = "must supply at least one element in the allow list"; goto loaderr;
@@ -756,6 +770,15 @@ void loadServerConfigFromString(char *config) {
} }
for (int i = 1; i < argc; i++) for (int i = 1; i < argc; i++)
g_pserver->tls_allowlist.emplace(argv[i], strlen(argv[i])); g_pserver->tls_allowlist.emplace(argv[i], strlen(argv[i]));
} else if (!strcasecmp(argv[0], "tls-auditlog-blocklist")) {
if (argc < 2) {
err = "must supply at least one element in the block list"; goto loaderr;
}
if (!g_pserver->tls_auditlog_blocklist.empty()) {
err = "tls-auditlog-blocklist may only be set once"; goto loaderr;
}
for (int i = 1; i < argc; i++)
g_pserver->tls_auditlog_blocklist.emplace(argv[i], strlen(argv[i]));
} else if (!strcasecmp(argv[0], "version-override") && argc == 2) { } else if (!strcasecmp(argv[0], "version-override") && argc == 2) {
KEYDB_SET_VERSION = zstrdup(argv[1]); KEYDB_SET_VERSION = zstrdup(argv[1]);
serverLog(LL_WARNING, "Warning version is overriden to: %s\n", KEYDB_SET_VERSION); serverLog(LL_WARNING, "Warning version is overriden to: %s\n", KEYDB_SET_VERSION);
@@ -2098,7 +2121,10 @@ static void sdsConfigGet(client *c, typeData data) {
} }
static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) { static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
rewriteConfigSdsOption(state, name, *(data.sds.config), data.sds.default_value ? sdsnew(data.sds.default_value) : NULL); sds sdsDefault = data.sds.default_value ? sdsnew(data.sds.default_value) : NULL;
rewriteConfigSdsOption(state, name, *(data.sds.config), sdsDefault);
if (sdsDefault)
sdsfree(sdsDefault);
} }
@@ -2455,6 +2481,32 @@ static int isValidAOFfilename(char *val, const char **err) {
return 1; return 1;
} }
static int isValidS3Bucket(char *s3bucket, const char **err) {
int status = EXIT_FAILURE;
pid_t pid = fork();
if (pid < 0)
{
*err = "couldn't fork to call aws cli";
return 0;
}
if (pid == 0)
{
execlp("aws", "aws", "s3", "ls", s3bucket, nullptr);
exit(EXIT_FAILURE);
}
else
{
waitpid(pid, &status, 0);
}
if (status != EXIT_SUCCESS) {
*err = "could not access s3 bucket";
return 0;
}
return 1;
}
/* Validate specified string is a valid proc-title-template */ /* Validate specified string is a valid proc-title-template */
static int isValidProcTitleTemplate(char *val, const char **err) { static int isValidProcTitleTemplate(char *val, const char **err) {
if (!validateProcTitleTemplate(val)) { if (!validateProcTitleTemplate(val)) {
@@ -2545,6 +2597,19 @@ static int updateMaxmemory(long long val, long long prev, const char **err) {
return 1; return 1;
} }
static int updateFlashMaxmemory(long long val, long long prev, const char **err) {
UNUSED(prev);
UNUSED(err);
if (val && g_pserver->m_pstorageFactory) {
size_t used = g_pserver->m_pstorageFactory->totalDiskspaceUsed();
if ((unsigned long long)val < used) {
serverLog(LL_WARNING,"WARNING: the new maxstorage value set via CONFIG SET (%llu) is smaller than the current storage usage (%zu). This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.", g_pserver->maxstorage, used);
}
performEvictions(false /*fPreSnapshot*/);
}
return 1;
}
static int updateGoodSlaves(long long val, long long prev, const char **err) { static int updateGoodSlaves(long long val, long long prev, const char **err) {
UNUSED(val); UNUSED(val);
UNUSED(prev); UNUSED(prev);
@@ -2587,7 +2652,7 @@ static int updateMaxclients(long long val, long long prev, const char **err) {
adjustOpenFilesLimit(); adjustOpenFilesLimit();
if (g_pserver->maxclients != val) { if (g_pserver->maxclients != val) {
static char msg[128]; static char msg[128];
snprintf(msg, 128, "The operating system is not able to handle the specified number of clients, try with %d", g_pserver->maxclients); snprintf(msg, sizeof(msg), "The operating system is not able to handle the specified number of clients, try with %d", g_pserver->maxclients);
*err = msg; *err = msg;
if (g_pserver->maxclients > prev) { if (g_pserver->maxclients > prev) {
g_pserver->maxclients = prev; g_pserver->maxclients = prev;
@@ -2626,7 +2691,7 @@ static int updateMaxclients(long long val, long long prev, const char **err) {
if (res != AE_OK){ if (res != AE_OK){
static char msg[128]; static char msg[128];
snprintf(msg, 128,"Failed to post the request to change setsize for Thread %d", iel); snprintf(msg, sizeof(msg),"Failed to post the request to change setsize for Thread %d", iel);
*err = msg; *err = msg;
return 0; return 0;
} }
@@ -2770,7 +2835,6 @@ standardConfig configs[] = {
createBoolConfig("replica-serve-stale-data", "slave-serve-stale-data", MODIFIABLE_CONFIG, g_pserver->repl_serve_stale_data, 1, NULL, NULL), createBoolConfig("replica-serve-stale-data", "slave-serve-stale-data", MODIFIABLE_CONFIG, g_pserver->repl_serve_stale_data, 1, NULL, NULL),
createBoolConfig("replica-read-only", "slave-read-only", MODIFIABLE_CONFIG, g_pserver->repl_slave_ro, 1, NULL, NULL), createBoolConfig("replica-read-only", "slave-read-only", MODIFIABLE_CONFIG, g_pserver->repl_slave_ro, 1, NULL, NULL),
createBoolConfig("replica-ignore-maxmemory", "slave-ignore-maxmemory", MODIFIABLE_CONFIG, g_pserver->repl_slave_ignore_maxmemory, 1, NULL, NULL), createBoolConfig("replica-ignore-maxmemory", "slave-ignore-maxmemory", MODIFIABLE_CONFIG, g_pserver->repl_slave_ignore_maxmemory, 1, NULL, NULL),
createBoolConfig("multi-master", NULL, IMMUTABLE_CONFIG, g_pserver->enable_multimaster,CONFIG_DEFAULT_ENABLE_MULTIMASTER, NULL, NULL),
createBoolConfig("jemalloc-bg-thread", NULL, MODIFIABLE_CONFIG, cserver.jemalloc_bg_thread, 1, NULL, updateJemallocBgThread), createBoolConfig("jemalloc-bg-thread", NULL, MODIFIABLE_CONFIG, cserver.jemalloc_bg_thread, 1, NULL, updateJemallocBgThread),
createBoolConfig("activedefrag", NULL, MODIFIABLE_CONFIG, cserver.active_defrag_enabled, 0, isValidActiveDefrag, NULL), createBoolConfig("activedefrag", NULL, MODIFIABLE_CONFIG, cserver.active_defrag_enabled, 0, isValidActiveDefrag, NULL),
createBoolConfig("syslog-enabled", NULL, IMMUTABLE_CONFIG, g_pserver->syslog_enabled, 0, NULL, NULL), createBoolConfig("syslog-enabled", NULL, IMMUTABLE_CONFIG, g_pserver->syslog_enabled, 0, NULL, NULL),
@@ -2778,7 +2842,7 @@ standardConfig configs[] = {
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, g_pserver->aof_enabled, 0, NULL, updateAppendonly), createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, g_pserver->aof_enabled, 0, NULL, updateAppendonly),
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, g_pserver->cluster_allow_reads_when_down, 0, NULL, NULL), createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, g_pserver->cluster_allow_reads_when_down, 0, NULL, NULL),
createBoolConfig("delete-on-evict", NULL, MODIFIABLE_CONFIG, cserver.delete_on_evict, 0, NULL, NULL), createBoolConfig("delete-on-evict", NULL, MODIFIABLE_CONFIG, cserver.delete_on_evict, 0, NULL, NULL),
createBoolConfig("use-fork", NULL, IMMUTABLE_CONFIG, cserver.fForkBgSave, 0, NULL, NULL), createBoolConfig("use-fork", NULL, IMMUTABLE_CONFIG, cserver.fForkBgSave, 1, NULL, NULL),
createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, fDummy, 0, NULL, NULL), createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, fDummy, 0, NULL, NULL),
createBoolConfig("time-thread-priority", NULL, IMMUTABLE_CONFIG, cserver.time_thread_priority, 0, NULL, NULL), createBoolConfig("time-thread-priority", NULL, IMMUTABLE_CONFIG, cserver.time_thread_priority, 0, NULL, NULL),
createBoolConfig("prefetch-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->prefetch_enabled, 1, NULL, NULL), createBoolConfig("prefetch-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->prefetch_enabled, 1, NULL, NULL),
@@ -2802,6 +2866,7 @@ standardConfig configs[] = {
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->cluster_announce_ip, NULL, NULL, NULL), createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->cluster_announce_ip, NULL, NULL, NULL),
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL), createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL),
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL), createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL),
createStringConfig("db-s3-object", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->rdb_s3bucketpath, NULL, isValidS3Bucket, NULL),
createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->aof_filename, "appendonly.aof", isValidAOFfilename, NULL), createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->aof_filename, "appendonly.aof", isValidAOFfilename, NULL),
createStringConfig("server_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->server_cpulist, NULL, NULL, NULL), createStringConfig("server_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->server_cpulist, NULL, NULL, NULL),
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bio_cpulist, NULL, NULL, NULL), createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bio_cpulist, NULL, NULL, NULL),
@@ -2856,7 +2921,7 @@ standardConfig configs[] = {
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->key_load_delay, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->key_load_delay, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, cserver.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */ createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, g_pserver->active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */
createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ), createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ),
createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves), createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves),
createIntConfig("min-replicas-max-lag", "min-slaves-max-lag", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_max_lag, 10, INTEGER_CONFIG, NULL, updateGoodSlaves), createIntConfig("min-replicas-max-lag", "min-slaves-max-lag", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_max_lag, 10, INTEGER_CONFIG, NULL, updateGoodSlaves),
@@ -2867,6 +2932,7 @@ standardConfig configs[] = {
/* Unsigned int configs */ /* Unsigned int configs */
createUIntConfig("maxclients", NULL, MODIFIABLE_CONFIG, 1, UINT_MAX, g_pserver->maxclients, 10000, INTEGER_CONFIG, NULL, updateMaxclients), createUIntConfig("maxclients", NULL, MODIFIABLE_CONFIG, 1, UINT_MAX, g_pserver->maxclients, 10000, INTEGER_CONFIG, NULL, updateMaxclients),
createUIntConfig("loading-process-events-interval-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_keys, 8192, MEMORY_CONFIG, NULL, NULL), createUIntConfig("loading-process-events-interval-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_keys, 8192, MEMORY_CONFIG, NULL, NULL),
createUIntConfig("maxclients-reserved", NULL, MODIFIABLE_CONFIG, 0, 100, g_pserver->maxclientsReserved, 0, INTEGER_CONFIG, NULL, NULL),
/* Unsigned Long configs */ /* Unsigned Long configs */
createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, cserver.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */ createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, cserver.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */
@@ -2880,13 +2946,14 @@ standardConfig configs[] = {
createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("proto-max-bulk-len", NULL, MODIFIABLE_CONFIG, 1024*1024, LLONG_MAX, g_pserver->proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */ createLongLongConfig("proto-max-bulk-len", NULL, MODIFIABLE_CONFIG, 1024*1024, LLONG_MAX, g_pserver->proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */
createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, g_pserver->repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */ createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, g_pserver->repl_backlog_config_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */
createLongLongConfig("repl-backlog-disk-reserve", NULL, IMMUTABLE_CONFIG, 0, LLONG_MAX, cserver.repl_backlog_disk_size, 0, MEMORY_CONFIG, NULL, NULL), createLongLongConfig("repl-backlog-disk-reserve", NULL, IMMUTABLE_CONFIG, 0, LLONG_MAX, cserver.repl_backlog_disk_size, 0, MEMORY_CONFIG, NULL, NULL),
createLongLongConfig("max-snapshot-slip", NULL, MODIFIABLE_CONFIG, 0, 5000, g_pserver->snapshot_slip, 400, 0, NULL, NULL), createLongLongConfig("max-snapshot-slip", NULL, MODIFIABLE_CONFIG, 0, 5000, g_pserver->snapshot_slip, 400, 0, NULL, NULL),
createLongLongConfig("max-rand-count", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX/2, g_pserver->rand_total_threshold, LONG_MAX/2, 0, NULL, NULL),
/* Unsigned Long Long configs */ /* Unsigned Long Long configs */
createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory), createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),
createULongLongConfig("maxstorage", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->maxstorage, 0, MEMORY_CONFIG, NULL, NULL), createULongLongConfig("maxstorage", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->maxstorage, 0, MEMORY_CONFIG, NULL, updateFlashMaxmemory),
/* Size_t configs */ /* Size_t configs */
createSizeTConfig("hash-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->hash_max_ziplist_entries, 512, INTEGER_CONFIG, NULL, NULL), createSizeTConfig("hash-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->hash_max_ziplist_entries, 512, INTEGER_CONFIG, NULL, NULL),
@@ -2910,6 +2977,13 @@ standardConfig configs[] = {
createBoolConfig("allow-write-during-load", NULL, MODIFIABLE_CONFIG, g_pserver->fWriteDuringActiveLoad, 0, NULL, NULL), createBoolConfig("allow-write-during-load", NULL, MODIFIABLE_CONFIG, g_pserver->fWriteDuringActiveLoad, 0, NULL, NULL),
createBoolConfig("force-backlog-disk-reserve", NULL, MODIFIABLE_CONFIG, cserver.force_backlog_disk, 0, NULL, NULL), createBoolConfig("force-backlog-disk-reserve", NULL, MODIFIABLE_CONFIG, cserver.force_backlog_disk, 0, NULL, NULL),
createBoolConfig("soft-shutdown", NULL, MODIFIABLE_CONFIG, g_pserver->config_soft_shutdown, 0, NULL, NULL), createBoolConfig("soft-shutdown", NULL, MODIFIABLE_CONFIG, g_pserver->config_soft_shutdown, 0, NULL, NULL),
createBoolConfig("flash-disable-key-cache", NULL, MODIFIABLE_CONFIG, g_pserver->flash_disable_key_cache, 0, NULL, NULL),
createSizeTConfig("semi-ordered-set-bucket-size", NULL, MODIFIABLE_CONFIG, 0, 1024, g_semiOrderedSetTargetBucketSize, 0, INTEGER_CONFIG, NULL, NULL),
createSDSConfig("availability-zone", NULL, MODIFIABLE_CONFIG, 0, g_pserver->sdsAvailabilityZone, "", NULL, NULL),
createIntConfig("overload-protect-percent", NULL, MODIFIABLE_CONFIG, 0, 200, g_pserver->overload_protect_threshold, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("force-eviction-percent", NULL, MODIFIABLE_CONFIG, 0, 100, g_pserver->force_eviction_percent, 0, INTEGER_CONFIG, NULL, NULL),
createBoolConfig("enable-async-rehash", NULL, MODIFIABLE_CONFIG, g_pserver->enable_async_rehash, 1, NULL, NULL),
createBoolConfig("enable-keydb-fastsync", NULL, MODIFIABLE_CONFIG, g_pserver->fEnableFastSync, 0, NULL, NULL),
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
createIntConfig("tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->tls_port, 0, INTEGER_CONFIG, NULL, updateTLSPort), /* TCP port. */ createIntConfig("tls-port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->tls_port, 0, INTEGER_CONFIG, NULL, updateTLSPort), /* TCP port. */
+2 -6
View File
@@ -41,14 +41,10 @@
#define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb" #define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb"
/* Define redis_fstat to fstat or fstat64() */ /* Define redis_fstat to fstat */
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
#define redis_fstat fstat64
#define redis_stat stat64
#else
#define redis_fstat fstat #define redis_fstat fstat
#define redis_stat stat #define redis_stat stat
#endif
/* Test for proc filesystem */ /* Test for proc filesystem */
#ifdef __linux__ #ifdef __linux__
+5
View File
@@ -161,6 +161,11 @@ static void connSocketClose(connection *conn) {
return; return;
} }
if (conn->fprint) {
zfree(conn->fprint);
conn->fprint = NULL;
}
zfree(conn); zfree(conn);
} }
+2
View File
@@ -51,6 +51,7 @@ typedef enum {
#define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */ #define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */
#define CONN_FLAG_READ_THREADSAFE (1<<2) #define CONN_FLAG_READ_THREADSAFE (1<<2)
#define CONN_FLAG_WRITE_THREADSAFE (1<<3) #define CONN_FLAG_WRITE_THREADSAFE (1<<3)
#define CONN_FLAG_AUDIT_LOGGING_REQUIRED (1<<4)
#define CONN_TYPE_SOCKET 1 #define CONN_TYPE_SOCKET 1
#define CONN_TYPE_TLS 2 #define CONN_TYPE_TLS 2
@@ -86,6 +87,7 @@ struct connection {
ConnectionCallbackFunc write_handler; ConnectionCallbackFunc write_handler;
ConnectionCallbackFunc read_handler; ConnectionCallbackFunc read_handler;
int fd; int fd;
char* fprint;
}; };
/* The connection module does not deal with listening and accepting sockets, /* The connection module does not deal with listening and accepting sockets,
+223 -210
View File
@@ -55,8 +55,7 @@ struct dbBackup {
int expireIfNeeded(redisDb *db, robj *key, robj *o); int expireIfNeeded(redisDb *db, robj *key, robj *o);
void slotToKeyUpdateKeyCore(const char *key, size_t keylen, int add); void slotToKeyUpdateKeyCore(const char *key, size_t keylen, int add);
std::unique_ptr<expireEntry> deserializeExpire(sds key, const char *str, size_t cch, size_t *poffset); std::unique_ptr<expireEntry> deserializeExpire(const char *str, size_t cch, size_t *poffset);
sds serializeStoredObjectAndExpire(redisDbPersistentData *db, const char *key, robj_roptr o);
dictType dictChangeDescType { dictType dictChangeDescType {
dictSdsHash, /* hash function */ dictSdsHash, /* hash function */
@@ -83,6 +82,7 @@ void updateExpire(redisDb *db, sds key, robj *valOld, robj *valNew)
serverAssert(db->FKeyExpires((const char*)key)); serverAssert(db->FKeyExpires((const char*)key));
valNew->expire = std::move(valOld->expire);
valNew->SetFExpires(true); valNew->SetFExpires(true);
valOld->SetFExpires(false); valOld->SetFExpires(false);
return; return;
@@ -93,7 +93,7 @@ static void lookupKeyUpdateObj(robj *val, int flags)
/* Update the access time for the ageing algorithm. /* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger * Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */ * a copy on write madness. */
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)) if (!hasActiveChildProcessOrBGSave() && !(flags & LOOKUP_NOTOUCH))
{ {
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) { if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val); updateLFU(val);
@@ -281,8 +281,8 @@ robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
return o; return o;
} }
bool dbAddCore(redisDb *db, sds key, robj *val, bool fUpdateMvcc, bool fAssumeNew = false, dict_iter *piterExisting = nullptr) { bool dbAddCore(redisDb *db, sds key, robj *val, bool fUpdateMvcc, bool fAssumeNew = false, dict_iter *piterExisting = nullptr, bool fValExpires = false) {
serverAssert(!val->FExpires()); serverAssert(fValExpires || !val->FExpires());
sds copy = sdsdupshared(key); sds copy = sdsdupshared(key);
uint64_t mvcc = getMvccTstamp(); uint64_t mvcc = getMvccTstamp();
@@ -1059,7 +1059,7 @@ void keysCommand(client *c) {
sds pattern = szFromObj(c->argv[1]); sds pattern = szFromObj(c->argv[1]);
const redisDbPersistentDataSnapshot *snapshot = nullptr; const redisDbPersistentDataSnapshot *snapshot = nullptr;
if (!(c->flags & (CLIENT_MULTI | CLIENT_BLOCKED))) if (!(c->flags & (CLIENT_MULTI | CLIENT_BLOCKED | CLIENT_DENY_BLOCKING)) && !(serverTL->in_eval || serverTL->in_exec))
snapshot = c->db->createSnapshot(c->mvccCheckpoint, true /* fOptional */); snapshot = c->db->createSnapshot(c->mvccCheckpoint, true /* fOptional */);
if (snapshot != nullptr) if (snapshot != nullptr)
{ {
@@ -1224,7 +1224,7 @@ void scanGenericCommand(client *c, robj_roptr o, unsigned long cursor) {
} }
} }
if (o == nullptr && count >= 100) if (o == nullptr && count >= 100 && !(serverTL->in_eval || serverTL->in_exec))
{ {
// Do an async version // Do an async version
if (c->asyncCommand( if (c->asyncCommand(
@@ -1494,15 +1494,6 @@ void renameGenericCommand(client *c, int nx) {
incrRefCount(o); incrRefCount(o);
std::unique_ptr<expireEntry> spexpire;
{ // scope pexpireOld since it will be invalid soon
std::unique_lock<fastlock> ul(g_expireLock);
expireEntry *pexpireOld = c->db->getExpire(c->argv[1]);
if (pexpireOld != nullptr)
spexpire = std::make_unique<expireEntry>(std::move(*pexpireOld));
}
if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { if (lookupKeyWrite(c->db,c->argv[2]) != NULL) {
if (nx) { if (nx) {
decrRefCount(o); decrRefCount(o);
@@ -1513,10 +1504,12 @@ void renameGenericCommand(client *c, int nx) {
* with the same name. */ * with the same name. */
dbDelete(c->db,c->argv[2]); dbDelete(c->db,c->argv[2]);
} }
bool fExpires = o->FExpires();
long long whenT = o->expire.when();
dbDelete(c->db,c->argv[1]); dbDelete(c->db,c->argv[1]);
dbAdd(c->db,c->argv[2],o); o->SetFExpires(fExpires);
if (spexpire != nullptr) dbAddCore(c->db,szFromObj(c->argv[2]),o,true /*fUpdateMvcc*/,true/*fAssumeNew*/,nullptr,true/*fValExpires*/);
setExpire(c,c->db,c->argv[2],std::move(*spexpire)); serverAssert(whenT == o->expire.when()); // dbDelete and dbAdd must not modify the expire, just the FExpire bit
signalModifiedKey(c,c->db,c->argv[1]); signalModifiedKey(c,c->db,c->argv[1]);
signalModifiedKey(c,c->db,c->argv[2]); signalModifiedKey(c,c->db,c->argv[2]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_from", notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_from",
@@ -1579,22 +1572,15 @@ void moveCommand(client *c) {
return; return;
} }
std::unique_ptr<expireEntry> spexpire;
{ // scope pexpireOld
std::unique_lock<fastlock> ul(g_expireLock);
expireEntry *pexpireOld = c->db->getExpire(c->argv[1]);
if (pexpireOld != nullptr)
spexpire = std::make_unique<expireEntry>(std::move(*pexpireOld));
}
if (o->FExpires())
removeExpire(c->db,c->argv[1]);
serverAssert(!o->FExpires());
incrRefCount(o); incrRefCount(o);
bool fExpire = o->FExpires();
long long whenT = o->expire.when();
dbDelete(src,c->argv[1]); dbDelete(src,c->argv[1]);
g_pserver->dirty++; g_pserver->dirty++;
dbAdd(dst,c->argv[1],o); o->SetFExpires(fExpire);
if (spexpire != nullptr) setExpire(c,dst,c->argv[1],std::move(*spexpire)); dbAddCore(dst, szFromObj(c->argv[1]), o, true /*fUpdateMvcc*/, true /*fAssumeNew*/, nullptr, true /*fValExpires*/);
serverAssert(whenT == o->expire.when()); // add/delete must not modify the expire time
signalModifiedKey(c,src,c->argv[1]); signalModifiedKey(c,src,c->argv[1]);
signalModifiedKey(c,dst,c->argv[1]); signalModifiedKey(c,dst,c->argv[1]);
@@ -1662,7 +1648,7 @@ void copyCommand(client *c) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} }
expire = c->db->getExpire(key); expire = o->FExpires() ? &o->expire : nullptr;
/* Return zero if the key already exists in the target DB. /* Return zero if the key already exists in the target DB.
* If REPLACE option is selected, delete newkey from targetDB. */ * If REPLACE option is selected, delete newkey from targetDB. */
@@ -1742,8 +1728,12 @@ int dbSwapDatabases(int id1, int id2) {
* ready_keys and watched_keys, since we want clients to * ready_keys and watched_keys, since we want clients to
* remain in the same DB they were. so put them back */ * remain in the same DB they were. so put them back */
std::swap(g_pserver->db[id1]->blocking_keys, g_pserver->db[id2]->blocking_keys); std::swap(g_pserver->db[id1]->blocking_keys, g_pserver->db[id2]->blocking_keys);
std::swap(g_pserver->db[id2]->ready_keys, g_pserver->db[id2]->ready_keys); std::swap(g_pserver->db[id1]->ready_keys, g_pserver->db[id2]->ready_keys);
std::swap(g_pserver->db[id2]->watched_keys, g_pserver->db[id2]->watched_keys); std::swap(g_pserver->db[id1]->watched_keys, g_pserver->db[id2]->watched_keys);
/* Don't swap the redisdb id, as it is expected to be same as database index everywhere else.
For example, flushdb, master/slave replication, etc. */
std::swap(g_pserver->db[id1]->id, g_pserver->db[id2]->id);
/* Now we need to handle clients blocked on lists: as an effect /* Now we need to handle clients blocked on lists: as an effect
* of swapping the two DBs, a client that was waiting for list * of swapping the two DBs, a client that was waiting for list
@@ -1768,6 +1758,10 @@ int dbSwapDatabases(int id1, int id2) {
void swapdbCommand(client *c) { void swapdbCommand(client *c) {
int id1, id2; int id1, id2;
listNode *ln;
listIter li;
client *cl;
/* Not allowed in cluster mode: we have just DB 0 there. */ /* Not allowed in cluster mode: we have just DB 0 there. */
if (g_pserver->cluster_enabled) { if (g_pserver->cluster_enabled) {
addReplyError(c,"SWAPDB is not allowed in cluster mode"); addReplyError(c,"SWAPDB is not allowed in cluster mode");
@@ -1791,7 +1785,30 @@ void swapdbCommand(client *c) {
RedisModuleSwapDbInfo si = {REDISMODULE_SWAPDBINFO_VERSION,(int32_t)id1,(int32_t)id2}; RedisModuleSwapDbInfo si = {REDISMODULE_SWAPDBINFO_VERSION,(int32_t)id1,(int32_t)id2};
moduleFireServerEvent(REDISMODULE_EVENT_SWAPDB,0,&si); moduleFireServerEvent(REDISMODULE_EVENT_SWAPDB,0,&si);
g_pserver->dirty++; g_pserver->dirty++;
// Persist the databse index to storage dbid mapping into FLASH for later recovery.
if (g_pserver->m_pstorageFactory != nullptr && g_pserver->metadataDb != nullptr) {
std::string dbid_key = "db-" + std::to_string(id1);
g_pserver->metadataDb->insert(dbid_key.c_str(), dbid_key.length(), &g_pserver->db[id1]->storage_id, sizeof(g_pserver->db[id1]->storage_id), true);
dbid_key = "db-" + std::to_string(id2);
g_pserver->metadataDb->insert(dbid_key.c_str(), dbid_key.length(), &g_pserver->db[id2]->storage_id, sizeof(g_pserver->db[id2]->storage_id), true);
}
addReply(c,shared.ok); addReply(c,shared.ok);
listRewind(g_pserver->clients,&li);
while ((ln = listNext(&li)) != NULL) {
cl = reinterpret_cast<struct client*>(listNodeValue(ln));
std::unique_lock<decltype(cl->lock)> lock(cl->lock);
if (cl->db->id == g_pserver->db[id1]->id) {
cl->db = g_pserver->db[id2];
updateDBWatchedKey(id1, cl);
}
else if (cl->db->id == g_pserver->db[id2]->id) {
cl->db = g_pserver->db[id1];
updateDBWatchedKey(id2, cl);
}
}
} }
} }
@@ -1806,63 +1823,48 @@ int redisDbPersistentData::removeExpire(robj *key, dict_iter itr) {
/* An expire may only be removed if there is a corresponding entry in the /* An expire may only be removed if there is a corresponding entry in the
* main dict. Otherwise, the key will never be freed. */ * main dict. Otherwise, the key will never be freed. */
serverAssertWithInfo(NULL,key,itr != nullptr); serverAssertWithInfo(NULL,key,itr != nullptr);
std::unique_lock<fastlock> ul(g_expireLock);
robj *val = itr.val(); robj *val = itr.val();
if (!val->FExpires()) if (!val->FExpires())
return 0; return 0;
trackkey(key, true /* fUpdate */); trackkey(key, true /* fUpdate */);
auto itrExpire = m_setexpire->find(itr.key());
serverAssert(itrExpire != m_setexpire->end());
m_setexpire->erase(itrExpire);
val->SetFExpires(false); val->SetFExpires(false);
serverAssert(m_numexpires > 0);
m_numexpires--;
return 1; return 1;
} }
int redisDbPersistentData::removeSubkeyExpire(robj *key, robj *subkey) { int redisDbPersistentData::removeSubkeyExpire(robj *key, robj *subkey) {
auto de = find(szFromObj(key)); auto de = find(szFromObj(key));
serverAssertWithInfo(NULL,key,de != nullptr); serverAssertWithInfo(NULL,key,de != nullptr);
std::unique_lock<fastlock> ul(g_expireLock);
robj *val = de.val(); robj *val = de.val();
if (!val->FExpires()) if (!val->FExpires())
return 0; return 0;
auto itr = m_setexpire->find(de.key()); if (!val->expire.FFat())
serverAssert(itr != m_setexpire->end());
serverAssert(itr->key() == de.key());
if (!itr->FFat())
return 0; return 0;
int found = 0; int found = 0;
for (auto subitr : *itr) for (auto subitr : val->expire)
{ {
if (subitr.subkey() == nullptr) if (subitr.subkey() == nullptr)
continue; continue;
if (sdscmp((sds)subitr.subkey(), szFromObj(subkey)) == 0) if (sdscmp((sds)subitr.subkey(), szFromObj(subkey)) == 0)
{ {
itr->erase(subitr); val->expire.erase(subitr);
found = 1; found = 1;
break; break;
} }
} }
if (itr->pfatentry()->size() == 0) if (val->expire.pfatentry()->size() == 0)
this->removeExpire(key, de); this->removeExpire(key, de);
return found; return found;
} }
void redisDbPersistentData::resortExpire(expireEntry &e)
{
std::unique_lock<fastlock> ul(g_expireLock);
auto itr = m_setexpire->find(e.key());
expireEntry eT = std::move(e);
m_setexpire->erase(itr);
m_setexpire->insert(eT);
}
/* Set an expire to the specified key. If the expire is set in the context /* 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 * 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 * to NULL. The 'when' parameter is the absolute unix time in milliseconds
@@ -1917,10 +1919,7 @@ void setExpire(client *c, redisDb *db, robj *key, expireEntry &&e)
if (kde.val()->FExpires()) if (kde.val()->FExpires())
removeExpire(db, key); removeExpire(db, key);
e.setKeyUnsafe(kde.key()); db->setExpire(kde.key(), std::move(e));
db->setExpire(std::move(e));
kde.val()->SetFExpires(true);
int writable_slave = listLength(g_pserver->masters) && g_pserver->repl_slave_ro == 0 && !g_pserver->fActiveReplica; int writable_slave = listLength(g_pserver->masters) && g_pserver->repl_slave_ro == 0 && !g_pserver->fActiveReplica;
if (c && writable_slave && !(c->flags & CLIENT_MASTER)) if (c && writable_slave && !(c->flags & CLIENT_MASTER))
@@ -1934,10 +1933,12 @@ expireEntry *redisDbPersistentDataSnapshot::getExpire(const char *key) {
if (expireSize() == 0) if (expireSize() == 0)
return nullptr; return nullptr;
auto itrExpire = m_setexpire->find(key); auto itr = find_cached_threadsafe(key);
if (itrExpire == m_setexpire->end()) if (itr == end())
return nullptr; return nullptr;
return itrExpire.operator->(); if (!itr.val()->FExpires())
return nullptr;
return &itr.val()->expire;
} }
const expireEntry *redisDbPersistentDataSnapshot::getExpire(const char *key) const const expireEntry *redisDbPersistentDataSnapshot::getExpire(const char *key) const
@@ -2038,15 +2039,13 @@ int keyIsExpired(const redisDbPersistentDataSnapshot *db, robj *key) {
/* Don't expire anything while loading. It will be done later. */ /* Don't expire anything while loading. It will be done later. */
if (g_pserver->loading) return 0; if (g_pserver->loading) return 0;
std::unique_lock<fastlock> ul(g_expireLock);
const expireEntry *pexpire = db->getExpire(key); const expireEntry *pexpire = db->getExpire(key);
mstime_t now; mstime_t now;
long long when;
if (pexpire == nullptr) return 0; /* No expire for this key */ if (pexpire == nullptr) return 0; /* No expire for this key */
long long when = pexpire->FGetPrimaryExpire(); if (!pexpire->FGetPrimaryExpire(&when))
if (when == INVALID_EXPIRE)
return 0; return 0;
/* If we are in the context of a Lua script, we pretend that time is /* If we are in the context of a Lua script, we pretend that time is
@@ -2479,25 +2478,28 @@ void slotToKeyUpdateKeyCore(const char *key, size_t keylen, int add) {
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
unsigned int hashslot = keyHashSlot(key,keylen); unsigned int hashslot = keyHashSlot(key,keylen);
unsigned char buf[64];
unsigned char *indexed = buf;
g_pserver->cluster->slots_keys_count[hashslot] += add ? 1 : -1; g_pserver->cluster->slots_keys_count[hashslot] += add ? 1 : -1;
if (keylen+2 > 64) indexed = (unsigned char*)zmalloc(keylen+2, MALLOC_SHARED);
indexed[0] = (hashslot >> 8) & 0xff; if (g_pserver->m_pstorageFactory == nullptr) {
indexed[1] = hashslot & 0xff; unsigned char buf[64];
memcpy(indexed+2,key,keylen); unsigned char *indexed = buf;
int fModified = false;
if (add) { if (keylen+2 > 64) indexed = (unsigned char*)zmalloc(keylen+2, MALLOC_SHARED);
fModified = raxInsert(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL,NULL); indexed[0] = (hashslot >> 8) & 0xff;
} else { indexed[1] = hashslot & 0xff;
fModified = raxRemove(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL); memcpy(indexed+2,key,keylen);
int fModified = false;
if (add) {
fModified = raxInsert(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
} else {
fModified = raxRemove(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL);
}
// This assert is disabled when a snapshot depth is >0 because prepOverwriteForSnapshot will add in a tombstone,
// this prevents ensure from adding the key to the dictionary which means the caller isn't aware we're already tracking
// the key.
serverAssert(fModified || g_pserver->db[0]->snapshot_depth() > 0);
if (indexed != buf) zfree(indexed);
} }
// This assert is disabled when a snapshot depth is >0 because prepOverwriteForSnapshot will add in a tombstone,
// this prevents ensure from adding the key to the dictionary which means the caller isn't aware we're already tracking
// the key.
serverAssert(fModified || g_pserver->db[0]->snapshot_depth() > 0);
if (indexed != buf) zfree(indexed);
} }
void slotToKeyAdd(sds key) { void slotToKeyAdd(sds key) {
@@ -2533,47 +2535,67 @@ void slotToKeyFlush(int async) {
* New objects are returned to represent keys, it's up to the caller to * New objects are returned to represent keys, it's up to the caller to
* decrement the reference count to release the keys names. */ * decrement the reference count to release the keys names. */
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) { unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) {
raxIterator iter; if (g_pserver->m_pstorageFactory != nullptr) {
int j = 0; int j = 0;
unsigned char indexed[2]; g_pserver->db[0]->getStorageCache()->enumerate_hashslot([&](const char *key, size_t cchKey, const void *, size_t )->bool {
keys[j++] = createStringObject(key, cchKey);
return --count;
}, hashslot);
return j;
} else {
raxIterator iter;
int j = 0;
unsigned char indexed[2];
indexed[0] = (hashslot >> 8) & 0xff; indexed[0] = (hashslot >> 8) & 0xff;
indexed[1] = hashslot & 0xff; indexed[1] = hashslot & 0xff;
raxStart(&iter,g_pserver->cluster->slots_to_keys); raxStart(&iter,g_pserver->cluster->slots_to_keys);
raxSeek(&iter,">=",indexed,2); raxSeek(&iter,">=",indexed,2);
while(count-- && raxNext(&iter)) { while(count-- && raxNext(&iter)) {
if (iter.key[0] != indexed[0] || iter.key[1] != indexed[1]) break; if (iter.key[0] != indexed[0] || iter.key[1] != indexed[1]) break;
keys[j++] = createStringObject((char*)iter.key+2,iter.key_len-2); keys[j++] = createStringObject((char*)iter.key+2,iter.key_len-2);
}
raxStop(&iter);
return j;
} }
raxStop(&iter);
return j;
} }
/* Remove all the keys in the specified hash slot. /* Remove all the keys in the specified hash slot.
* The number of removed items is returned. */ * The number of removed items is returned. */
unsigned int delKeysInSlot(unsigned int hashslot) { unsigned int delKeysInSlot(unsigned int hashslot) {
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
if (g_pserver->m_pstorageFactory != nullptr) {
raxIterator iter; int j = 0;
int j = 0; g_pserver->db[0]->getStorageCache()->enumerate_hashslot([&](const char *key, size_t cchKey, const void *, size_t )->bool {
unsigned char indexed[2]; robj *keyobj = createStringObject(key, cchKey);
dbDelete(g_pserver->db[0], keyobj);
decrRefCount(keyobj);
j++;
return true;
}, hashslot);
return j;
} else {
raxIterator iter;
int j = 0;
unsigned char indexed[2];
indexed[0] = (hashslot >> 8) & 0xff; indexed[0] = (hashslot >> 8) & 0xff;
indexed[1] = hashslot & 0xff; indexed[1] = hashslot & 0xff;
raxStart(&iter,g_pserver->cluster->slots_to_keys); raxStart(&iter,g_pserver->cluster->slots_to_keys);
while(g_pserver->cluster->slots_keys_count[hashslot]) { while(g_pserver->cluster->slots_keys_count[hashslot]) {
raxSeek(&iter,">=",indexed,2); raxSeek(&iter,">=",indexed,2);
raxNext(&iter); raxNext(&iter);
auto count = g_pserver->cluster->slots_keys_count[hashslot]; auto count = g_pserver->cluster->slots_keys_count[hashslot];
robj *key = createStringObject((char*)iter.key+2,iter.key_len-2); robj *key = createStringObject((char*)iter.key+2,iter.key_len-2);
dbDelete(g_pserver->db[0],key); dbDelete(g_pserver->db[0],key);
serverAssert(count > g_pserver->cluster->slots_keys_count[hashslot]); // we should have deleted something or we will be in an infinite loop serverAssert(count > g_pserver->cluster->slots_keys_count[hashslot]); // we should have deleted something or we will be in an infinite loop
decrRefCount(key); decrRefCount(key);
j++; j++;
}
raxStop(&iter);
return j;
} }
raxStop(&iter);
return j;
} }
unsigned int countKeysInSlot(unsigned int hashslot) { unsigned int countKeysInSlot(unsigned int hashslot) {
@@ -2585,7 +2607,6 @@ void redisDbPersistentData::initialize()
m_pdbSnapshot = nullptr; m_pdbSnapshot = nullptr;
m_pdict = dictCreate(&dbDictType,this); m_pdict = dictCreate(&dbDictType,this);
m_pdictTombstone = dictCreate(&dbTombstoneDictType,this); m_pdictTombstone = dictCreate(&dbTombstoneDictType,this);
m_setexpire = new(MALLOC_LOCAL) expireset();
m_fAllChanged = 0; m_fAllChanged = 0;
m_fTrackingChanges = 0; m_fTrackingChanges = 0;
} }
@@ -2607,14 +2628,25 @@ void clusterStorageLoadCallback(const char *rgchkey, size_t cch, void *)
slotToKeyUpdateKeyCore(rgchkey, cch, true /*add*/); slotToKeyUpdateKeyCore(rgchkey, cch, true /*add*/);
} }
void redisDb::initialize(int id) void moduleLoadCallback(const char * rgchKey, size_t, void *data) {
redisObjectStack keyobj;
initStaticStringObject(keyobj, const_cast<char *>(rgchKey));
moduleNotifyKeyspaceEvent(NOTIFY_LOADED, "loaded", &keyobj, *(int *)data);
}
void moduleClusterLoadCallback(const char * rgchKey, size_t cchKey, void *data) {
clusterStorageLoadCallback(rgchKey, cchKey, data);
moduleLoadCallback(rgchKey, cchKey, data);
}
void redisDb::initialize(int id, int storage_id /* default no storage */)
{ {
redisDbPersistentData::initialize(); redisDbPersistentData::initialize();
this->expireitr = setexpire()->end();
this->blocking_keys = dictCreate(&keylistDictType,NULL); this->blocking_keys = dictCreate(&keylistDictType,NULL);
this->ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL); this->ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
this->watched_keys = dictCreate(&keylistDictType,NULL); this->watched_keys = dictCreate(&keylistDictType,NULL);
this->id = id; this->id = id;
this->storage_id = storage_id;
this->avg_ttl = 0; this->avg_ttl = 0;
this->last_expire_set = 0; this->last_expire_set = 0;
this->defrag_later = listCreate(); this->defrag_later = listCreate();
@@ -2625,8 +2657,8 @@ void redisDb::storageProviderInitialize()
{ {
if (g_pserver->m_pstorageFactory != nullptr) if (g_pserver->m_pstorageFactory != nullptr)
{ {
IStorageFactory::key_load_iterator itr = (g_pserver->cluster_enabled) ? clusterStorageLoadCallback : nullptr; IStorageFactory::key_load_iterator itr = g_pserver->cluster_enabled ? moduleClusterLoadCallback : moduleLoadCallback;
this->setStorageProvider(StorageCache::create(g_pserver->m_pstorageFactory, id, itr, nullptr)); this->setStorageProvider(StorageCache::create(g_pserver->m_pstorageFactory, storage_id, itr, &id));
} }
} }
@@ -2656,6 +2688,8 @@ bool redisDbPersistentData::insert(char *key, robj *o, bool fAssumeNew, dict_ite
serverAssert(dictFind(m_pdictTombstone, key) != nullptr); serverAssert(dictFind(m_pdictTombstone, key) != nullptr);
} }
#endif #endif
if (o->FExpires())
++m_numexpires;
trackkey(key, false /* fUpdate */); trackkey(key, false /* fUpdate */);
} }
else else
@@ -2677,11 +2711,6 @@ void redisDbPersistentData::prepOverwriteForSnapshot(char *key)
auto itr = m_pdbSnapshot->find_cached_threadsafe(key); auto itr = m_pdbSnapshot->find_cached_threadsafe(key);
if (itr.key() != nullptr) if (itr.key() != nullptr)
{ {
if (itr.val()->FExpires()) {
// Note: I'm sure we could handle this, but its too risky at the moment.
// There are known bugs doing this with expires
return;
}
sds keyNew = sdsdupshared(itr.key()); sds keyNew = sdsdupshared(itr.key());
if (dictAdd(m_pdictTombstone, keyNew, (void*)dictHashKey(m_pdict, key)) != DICT_OK) if (dictAdd(m_pdictTombstone, keyNew, (void*)dictHashKey(m_pdict, key)) != DICT_OK)
sdsfree(keyNew); sdsfree(keyNew);
@@ -2703,7 +2732,7 @@ size_t redisDb::clear(bool fAsync, void(callback)(void*))
} else { } else {
redisDbPersistentData::clear(callback); redisDbPersistentData::clear(callback);
} }
expireitr = setexpire()->end(); expires_cursor = 0;
return removed; return removed;
} }
@@ -2716,56 +2745,64 @@ void redisDbPersistentData::clear(void(callback)(void*))
m_cnewKeysPending = 0; m_cnewKeysPending = 0;
m_fAllChanged++; m_fAllChanged++;
} }
delete m_setexpire;
m_setexpire = new (MALLOC_LOCAL) expireset();
if (m_spstorage != nullptr) if (m_spstorage != nullptr)
m_spstorage->clear(callback); m_spstorage->clear(callback);
dictEmpty(m_pdictTombstone,callback); dictEmpty(m_pdictTombstone,callback);
// To avoid issues with async rehash we completly free the old dict and create a fresh one
dictRelease(m_pdict);
dictRelease(m_pdictTombstone);
m_pdict = dictCreate(&dbDictType, this);
m_pdictTombstone = dictCreate(&dbTombstoneDictType, this);
m_pdbSnapshot = nullptr; m_pdbSnapshot = nullptr;
m_numexpires = 0;
} }
void redisDbPersistentData::setExpire(robj *key, robj *subkey, long long when) void redisDbPersistentData::setExpire(robj *key, robj *subkey, long long when)
{ {
/* Reuse the sds from the main dict in the expire dict */ /* Reuse the sds from the main dict in the expire dict */
std::unique_lock<fastlock> ul(g_expireLock);
dictEntry *kde = dictFind(m_pdict,ptrFromObj(key)); dictEntry *kde = dictFind(m_pdict,ptrFromObj(key));
serverAssertWithInfo(NULL,key,kde != NULL); serverAssertWithInfo(NULL,key,kde != NULL);
trackkey(key, true /* fUpdate */); trackkey(key, true /* fUpdate */);
if (((robj*)dictGetVal(kde))->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT) robj *o = (robj*)dictGetVal(kde);
if (o->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
{ {
// shared objects cannot have the expire bit set, create a real object // shared objects cannot have the expire bit set, create a real object
dictSetVal(m_pdict, kde, dupStringObject((robj*)dictGetVal(kde))); dictSetVal(m_pdict, kde, dupStringObject(o));
o = (robj*)dictGetVal(kde);
} }
const char *szSubKey = (subkey != nullptr) ? szFromObj(subkey) : nullptr; const char *szSubKey = (subkey != nullptr) ? szFromObj(subkey) : nullptr;
if (((robj*)dictGetVal(kde))->FExpires()) { if (o->FExpires()) {
auto itr = m_setexpire->find((sds)dictGetKey(kde)); o->expire.update(szSubKey, when);
serverAssert(itr != m_setexpire->end());
expireEntry eNew(std::move(*itr));
eNew.update(szSubKey, when);
m_setexpire->erase(itr);
m_setexpire->insert(eNew);
} }
else else
{ {
expireEntry e((sds)dictGetKey(kde), szSubKey, when); expireEntry e(szSubKey, when);
((robj*)dictGetVal(kde))->SetFExpires(true); o->expire = std::move(e);
m_setexpire->insert(e); o->SetFExpires(true);
++m_numexpires;
} }
} }
void redisDbPersistentData::setExpire(expireEntry &&e) void redisDbPersistentData::setExpire(const char *key, expireEntry &&e)
{ {
std::unique_lock<fastlock> ul(g_expireLock); trackkey(key, true /* fUpdate */);
trackkey(e.key(), true /* fUpdate */); auto itr = find(key);
m_setexpire->insert(e); if (!itr->FExpires())
m_numexpires++;
itr->expire = std::move(e);
itr->SetFExpires(true);
} }
bool redisDb::FKeyExpires(const char *key) bool redisDb::FKeyExpires(const char *key)
{ {
std::unique_lock<fastlock> ul(g_expireLock); auto itr = find(key);
return setexpireUnsafe()->find(key) != setexpire()->end(); if (itr == end())
return false;
return itr->FExpires();
} }
void redisDbPersistentData::updateValue(dict_iter itr, robj *val) void redisDbPersistentData::updateValue(dict_iter itr, robj *val)
@@ -2789,7 +2826,6 @@ void redisDbPersistentData::ensure(const char *sdsKey, dictEntry **pde)
serverAssert(m_refCount == 0); serverAssert(m_refCount == 0);
if (m_pdbSnapshot == nullptr && g_pserver->m_pstorageFactory == nullptr) if (m_pdbSnapshot == nullptr && g_pserver->m_pstorageFactory == nullptr)
return; return;
std::unique_lock<fastlock> ul(g_expireLock);
// First see if the key can be obtained from a snapshot // First see if the key can be obtained from a snapshot
if (*pde == nullptr && m_pdbSnapshot != nullptr) if (*pde == nullptr && m_pdbSnapshot != nullptr)
@@ -2811,7 +2847,11 @@ void redisDbPersistentData::ensure(const char *sdsKey, dictEntry **pde)
else else
{ {
sds strT = serializeStoredObject(itr.val()); sds strT = serializeStoredObject(itr.val());
robj *objNew = deserializeStoredObject(this, sdsKey, strT, sdslen(strT)); robj *objNew = deserializeStoredObject(strT, sdslen(strT));
if (itr->FExpires()) {
objNew->expire = itr->expire;
objNew->SetFExpires(true);
}
sdsfree(strT); sdsfree(strT);
dictAdd(m_pdict, keyNew, objNew); dictAdd(m_pdict, keyNew, objNew);
serverAssert(objNew->getrefcount(std::memory_order_relaxed) == 1); serverAssert(objNew->getrefcount(std::memory_order_relaxed) == 1);
@@ -2841,25 +2881,19 @@ LNotFound:
std::unique_ptr<expireEntry> spexpire; std::unique_ptr<expireEntry> spexpire;
m_spstorage->retrieve((sds)sdsKey, [&](const char *, size_t, const void *data, size_t cb){ m_spstorage->retrieve((sds)sdsKey, [&](const char *, size_t, const void *data, size_t cb){
size_t offset = 0; size_t offset = 0;
spexpire = deserializeExpire(sdsNewKey, (const char*)data, cb, &offset); spexpire = deserializeExpire((const char*)data, cb, &offset);
o = deserializeStoredObject(this, sdsNewKey, reinterpret_cast<const char*>(data) + offset, cb - offset); o = deserializeStoredObject(reinterpret_cast<const char*>(data) + offset, cb - offset);
serverAssert(o != nullptr); serverAssert(o != nullptr);
}); });
if (o != nullptr) if (o != nullptr)
{ {
dictAdd(m_pdict, sdsNewKey, o); dictAdd(m_pdict, sdsNewKey, o);
o->SetFExpires(spexpire != nullptr); o->SetFExpires(spexpire != nullptr);
if (spexpire != nullptr) {
if (spexpire != nullptr) o->expire = std::move(*spexpire);
{
auto itr = m_setexpire->find(sdsKey);
if (itr != m_setexpire->end())
m_setexpire->erase(itr);
m_setexpire->insert(std::move(*spexpire));
serverAssert(m_setexpire->find(sdsKey) != m_setexpire->end());
} }
serverAssert(o->FExpires() == (m_setexpire->find(sdsKey) != m_setexpire->end()));
g_pserver->stat_storage_provider_read_hits++; g_pserver->stat_storage_provider_read_hits++;
} else { } else {
sdsfree(sdsNewKey); sdsfree(sdsNewKey);
@@ -2869,17 +2903,11 @@ LNotFound:
*pde = dictFind(m_pdict, sdsKey); *pde = dictFind(m_pdict, sdsKey);
} }
} }
if (*pde != nullptr && dictGetVal(*pde) != nullptr)
{
robj *o = (robj*)dictGetVal(*pde);
serverAssert(o->FExpires() == (m_setexpire->find(sdsKey) != m_setexpire->end()));
}
} }
void redisDbPersistentData::storeKey(sds key, robj *o, bool fOverwrite) void redisDbPersistentData::storeKey(sds key, robj *o, bool fOverwrite)
{ {
sds temp = serializeStoredObjectAndExpire(this, key, o); sds temp = serializeStoredObjectAndExpire(o);
m_spstorage->insert(key, temp, sdslen(temp), fOverwrite); m_spstorage->insert(key, temp, sdslen(temp), fOverwrite);
sdsfree(temp); sdsfree(temp);
} }
@@ -2903,7 +2931,7 @@ void redisDbPersistentData::storeDatabase()
if (itr == nullptr) if (itr == nullptr)
return; return;
robj *o = itr.val(); robj *o = itr.val();
sds temp = serializeStoredObjectAndExpire(db, (const char*) itr.key(), o); sds temp = serializeStoredObjectAndExpire(o);
storage->insert((sds)key, temp, sdslen(temp), fUpdate); storage->insert((sds)key, temp, sdslen(temp), fUpdate);
sdsfree(temp); sdsfree(temp);
} }
@@ -2979,7 +3007,7 @@ void redisDbPersistentData::processChangesAsync(std::atomic<int> &pendingJobs)
while ((de = dictNext(di)) != nullptr) while ((de = dictNext(di)) != nullptr)
{ {
robj *o = (robj*)dictGetVal(de); robj *o = (robj*)dictGetVal(de);
sds temp = serializeStoredObjectAndExpire(this, (const char*) dictGetKey(de), o); sds temp = serializeStoredObjectAndExpire(o);
veckeys.push_back((sds)dictGetKey(de)); veckeys.push_back((sds)dictGetKey(de));
veccbkeys.push_back(sdslen((sds)dictGetKey(de))); veccbkeys.push_back(sdslen((sds)dictGetKey(de)));
vecvals.push_back(temp); vecvals.push_back(temp);
@@ -2994,8 +3022,16 @@ void redisDbPersistentData::processChangesAsync(std::atomic<int> &pendingJobs)
}); });
} }
void redisDbPersistentData::bulkStorageInsert(char **rgKeys, size_t *rgcbKeys, char **rgVals, size_t *rgcbVals, size_t celem) /* This function is to bulk insert directly to storage provider bypassing in memory, assumes rgKeys and rgVals are not sds strings */
void redisDbPersistentData::bulkDirectStorageInsert(char **rgKeys, size_t *rgcbKeys, char **rgVals, size_t *rgcbVals, size_t celem)
{ {
if (g_pserver->cluster_enabled) {
aeAcquireLock();
for (size_t i = 0; i < celem; i++) {
slotToKeyUpdateKeyCore(rgKeys[i], rgcbKeys[i], 1);
}
aeReleaseLock();
}
m_spstorage->bulkInsert(rgKeys, rgcbKeys, rgVals, rgcbVals, celem); m_spstorage->bulkInsert(rgKeys, rgcbKeys, rgVals, rgcbVals, celem);
} }
@@ -3035,9 +3071,7 @@ redisDbPersistentData::~redisDbPersistentData()
if (m_dictChanged) if (m_dictChanged)
dictRelease(m_dictChanged); dictRelease(m_dictChanged);
if (m_dictChangedStorageFlush) if (m_dictChangedStorageFlush)
dictRelease(m_dictChangedStorageFlush); dictRelease(m_dictChangedStorageFlush);
delete m_setexpire;
} }
dict_iter redisDbPersistentData::random() dict_iter redisDbPersistentData::random()
@@ -3191,7 +3225,7 @@ sds serializeExpire(const expireEntry *pexpire)
return str; return str;
} }
std::unique_ptr<expireEntry> deserializeExpire(sds key, const char *str, size_t cch, size_t *poffset) std::unique_ptr<expireEntry> deserializeExpire(const char *str, size_t cch, size_t *poffset)
{ {
unsigned celem; unsigned celem;
if (cch < sizeof(unsigned)) if (cch < sizeof(unsigned))
@@ -3223,24 +3257,21 @@ std::unique_ptr<expireEntry> deserializeExpire(sds key, const char *str, size_t
offset += sizeof(long long); offset += sizeof(long long);
if (spexpire == nullptr) if (spexpire == nullptr)
spexpire = std::make_unique<expireEntry>(key, subkey, when); spexpire = std::make_unique<expireEntry>(subkey, when);
else else
spexpire->update(subkey, when); spexpire->update(subkey, when);
if (subkey) if (subkey)
sdsfree(subkey); sdsfree(subkey);
} }
if (poffset != nullptr)
*poffset = offset; *poffset = offset;
return spexpire; return spexpire;
} }
sds serializeStoredObjectAndExpire(redisDbPersistentData *db, const char *key, robj_roptr o) sds serializeStoredObjectAndExpire(robj_roptr o)
{ {
auto itrExpire = db->setexpire()->find(key); const expireEntry *pexpire = o->FExpires() ? &o->expire : nullptr;
const expireEntry *pexpire = nullptr;
if (itrExpire != db->setexpire()->end())
pexpire = &(*itrExpire);
sds str = serializeExpire(pexpire); sds str = serializeExpire(pexpire);
str = serializeStoredObject(o, str); str = serializeStoredObject(o, str);
@@ -3257,7 +3288,7 @@ int dbnumFromDb(redisDb *db)
serverPanic("invalid database pointer"); serverPanic("invalid database pointer");
} }
bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command, bool fExecOK) void redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command)
{ {
if (m_spstorage == nullptr) { if (m_spstorage == nullptr) {
#if defined(__x86_64__) || defined(__i386__) #if defined(__x86_64__) || defined(__i386__)
@@ -3269,7 +3300,7 @@ bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command
const char *cmd = szFromObj(command.argv[0]); const char *cmd = szFromObj(command.argv[0]);
if (!strcasecmp(cmd, "set") || !strcasecmp(cmd, "get")) { if (!strcasecmp(cmd, "set") || !strcasecmp(cmd, "get")) {
if (c->db->m_spdbSnapshotHOLDER != nullptr) if (c->db->m_spdbSnapshotHOLDER != nullptr)
return false; // this is dangerous enough without a snapshot around return; // this is dangerous enough without a snapshot around
auto h = dictSdsHash(szFromObj(command.argv[1])); auto h = dictSdsHash(szFromObj(command.argv[1]));
for (int iht = 0; iht < 2; ++iht) { for (int iht = 0; iht < 2; ++iht) {
auto hT = h & c->db->m_pdict->ht[iht].sizemask; auto hT = h & c->db->m_pdict->ht[iht].sizemask;
@@ -3289,7 +3320,7 @@ bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command
} }
} }
#endif #endif
return false; return;
} }
AeLocker lock; AeLocker lock;
@@ -3299,10 +3330,10 @@ bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command
getKeysResult result = GETKEYS_RESULT_INIT; getKeysResult result = GETKEYS_RESULT_INIT;
auto cmd = lookupCommand(szFromObj(command.argv[0])); auto cmd = lookupCommand(szFromObj(command.argv[0]));
if (cmd == nullptr) if (cmd == nullptr)
return false; // Bad command? It's not for us to judge, just bail return; // Bad command? It's not for us to judge, just bail
if (command.argc < std::abs(cmd->arity)) if (command.argc < std::abs(cmd->arity))
return false; // Invalid number of args return; // Invalid number of args
int numkeys = getKeysFromCommand(cmd, command.argv, command.argc, &result); int numkeys = getKeysFromCommand(cmd, command.argv, command.argc, &result);
for (int ikey = 0; ikey < numkeys; ++ikey) for (int ikey = 0; ikey < numkeys; ++ikey)
@@ -3323,8 +3354,8 @@ bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command
robj *o = nullptr; robj *o = nullptr;
m_spstorage->retrieve((sds)szFromObj(objKey), [&](const char *, size_t, const void *data, size_t cb){ m_spstorage->retrieve((sds)szFromObj(objKey), [&](const char *, size_t, const void *data, size_t cb){
size_t offset = 0; size_t offset = 0;
spexpire = deserializeExpire(sharedKey, (const char*)data, cb, &offset); spexpire = deserializeExpire((const char*)data, cb, &offset);
o = deserializeStoredObject(this, sharedKey, reinterpret_cast<const char*>(data) + offset, cb - offset); o = deserializeStoredObject(reinterpret_cast<const char*>(data) + offset, cb - offset);
serverAssert(o != nullptr); serverAssert(o != nullptr);
}); });
@@ -3335,7 +3366,6 @@ bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command
} }
} }
bool fNoInsert = false;
if (!vecInserts.empty()) { if (!vecInserts.empty()) {
lock.arm(c); lock.arm(c);
for (auto &tuple : vecInserts) for (auto &tuple : vecInserts)
@@ -3351,28 +3381,18 @@ bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command
// While unlocked this was already ensured // While unlocked this was already ensured
decrRefCount(o); decrRefCount(o);
sdsfree(sharedKey); sdsfree(sharedKey);
fNoInsert = true;
} }
else else
{ {
if (spexpire != nullptr) { if (spexpire != nullptr) {
if (spexpire->when() < mstime()) { if (spexpire->when() < mstime()) {
fNoInsert = true;
break; break;
} }
} }
dictAdd(m_pdict, sharedKey, o); dictAdd(m_pdict, sharedKey, o);
o->SetFExpires(spexpire != nullptr);
if (spexpire != nullptr) if (spexpire != nullptr)
{ o->expire = std::move(*spexpire);
auto itr = m_setexpire->find(sharedKey); o->SetFExpires(spexpire != nullptr);
if (itr != m_setexpire->end())
m_setexpire->erase(itr);
m_setexpire->insert(std::move(*spexpire));
serverAssert(m_setexpire->find(sharedKey) != m_setexpire->end());
}
serverAssert(o->FExpires() == (m_setexpire->find(sharedKey) != m_setexpire->end()));
} }
} }
else else
@@ -3384,12 +3404,5 @@ bool redisDbPersistentData::prefetchKeysAsync(client *c, parsed_command &command
lock.disarm(); lock.disarm();
} }
if (fExecOK && !fNoInsert && cmd->proc == getCommand && !vecInserts.empty()) { return;
robj *o = std::get<1>(vecInserts[0]); }
if (o != nullptr) {
addReplyBulk(c, o);
return true;
}
}
return false;
}
+91 -35
View File
@@ -79,6 +79,7 @@ void printCrashReport(void);
void bugReportEnd(int killViaSignal, int sig); void bugReportEnd(int killViaSignal, int sig);
void logStackTrace(void *eip, int uplevel); void logStackTrace(void *eip, int uplevel);
void getTempFileName(char tmpfile[], int tmpfileNum); void getTempFileName(char tmpfile[], int tmpfileNum);
bool initializeStorageProvider(const char **err);
/* ================================= Debugging ============================== */ /* ================================= Debugging ============================== */
@@ -146,11 +147,10 @@ void mixStringObjectDigest(unsigned char *digest, robj_roptr o) {
* Note that this function does not reset the initial 'digest' passed, it * Note that this function does not reset the initial 'digest' passed, it
* will continue mixing this object digest to anything that was already * will continue mixing this object digest to anything that was already
* present. */ * present. */
void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj_roptr o) { void xorObjectDigest(unsigned char *digest, robj_roptr o) {
uint32_t aux = htonl(o->type); uint32_t aux = htonl(o->type);
mixDigest(digest,&aux,sizeof(aux)); mixDigest(digest,&aux,sizeof(aux));
std::unique_lock<fastlock> ul(g_expireLock); const expireEntry *pexpire = o->FExpires() ? &o->expire : nullptr;
expireEntry *pexpire = db->getExpire(keyobj);
long long expiretime = INVALID_EXPIRE; long long expiretime = INVALID_EXPIRE;
char buf[128]; char buf[128];
@@ -309,7 +309,7 @@ void computeDatasetDigest(unsigned char *final) {
mixDigest(final,&aux,sizeof(aux)); mixDigest(final,&aux,sizeof(aux));
/* Iterate this DB writing every entry */ /* Iterate this DB writing every entry */
db->iterate_threadsafe([final, db](const char *key, robj_roptr o)->bool { db->iterate_threadsafe([final](const char *key, robj_roptr o)->bool {
unsigned char digest[20]; unsigned char digest[20];
robj *keyobj; robj *keyobj;
@@ -318,7 +318,7 @@ void computeDatasetDigest(unsigned char *final) {
mixDigest(digest,key,sdslen(key)); mixDigest(digest,key,sdslen(key));
xorObjectDigest(db,keyobj,digest,o); xorObjectDigest(digest,o);
/* We can finally xor the key-val digest to the final digest */ /* We can finally xor the key-val digest to the final digest */
xorDigest(final,digest,20); xorDigest(final,digest,20);
@@ -341,10 +341,10 @@ void mallctl_int(client *c, robj **argv, int argc) {
} }
size_t sz = sizeof(old); size_t sz = sizeof(old);
while (sz > 0) { while (sz > 0) {
if ((ret=je_mallctl(szFromObj(argv[0]), &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) { if ((ret=mallctl(szFromObj(argv[0]), &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) {
if (ret == EPERM && argc > 1) { if (ret == EPERM && argc > 1) {
/* if this option is write only, try just writing to it. */ /* if this option is write only, try just writing to it. */
if (!(ret=je_mallctl(szFromObj(argv[0]), NULL, 0, &val, sz))) { if (!(ret=mallctl(szFromObj(argv[0]), NULL, 0, &val, sz))) {
addReply(c, shared.ok); addReply(c, shared.ok);
return; return;
} }
@@ -375,7 +375,7 @@ void mallctl_string(client *c, robj **argv, int argc) {
char *old; char *old;
size_t sz = sizeof(old); size_t sz = sizeof(old);
/* for strings, it seems we need to first get the old value, before overriding it. */ /* for strings, it seems we need to first get the old value, before overriding it. */
if ((rret=je_mallctl(szFromObj(argv[0]), &old, &sz, NULL, 0))) { if ((rret=mallctl(szFromObj(argv[0]), &old, &sz, NULL, 0))) {
/* return error unless this option is write only. */ /* return error unless this option is write only. */
if (!(rret == EPERM && argc > 1)) { if (!(rret == EPERM && argc > 1)) {
addReplyErrorFormat(c,"%s", strerror(rret)); addReplyErrorFormat(c,"%s", strerror(rret));
@@ -387,7 +387,7 @@ void mallctl_string(client *c, robj **argv, int argc) {
char **valref = &val; char **valref = &val;
if ((!strcmp(val,"VOID"))) if ((!strcmp(val,"VOID")))
valref = NULL, sz = 0; valref = NULL, sz = 0;
wret = je_mallctl(szFromObj(argv[0]), NULL, 0, valref, sz); wret = mallctl(szFromObj(argv[0]), NULL, 0, valref, sz);
} }
if (!rret) if (!rret)
addReplyBulkCString(c, old); addReplyBulkCString(c, old);
@@ -536,30 +536,75 @@ NULL
return; return;
} }
} }
if (g_pserver->m_pstorageFactory) {
protectClient(c);
/* The default behavior is to save the RDB file before loading for (int idb = 0; idb < cserver.dbnum; ++idb) {
* it back. */ if (g_pserver->db[idb]->processChanges(false))
if (save) { g_pserver->db[idb]->commitChanges();
rdbSaveInfo rsi, *rsiptr; g_pserver->db[idb]->storageProviderDelete();
rsiptr = rdbPopulateSaveInfo(&rsi); }
if (rdbSave(nullptr, rsiptr) != C_OK) { delete g_pserver->metadataDb;
addReply(c,shared.err);
if (flush) emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
delete g_pserver->m_pstorageFactory;
const char *err;
if (!initializeStorageProvider(&err))
{
serverLog(LL_WARNING, "Failed to initialize storage provider: %s",err);
exit(EXIT_FAILURE);
}
g_pserver->metadataDb = g_pserver->m_pstorageFactory->createMetadataDb();
for (int idb = 0; idb < cserver.dbnum; ++idb)
{
int dbid = idb;
std::string dbid_key = "db-" + std::to_string(idb);
g_pserver->metadataDb->retrieve(dbid_key.c_str(), dbid_key.length(), [&](const char *, size_t, const void *data, size_t){
dbid = *(int*)data;
});
delete g_pserver->db[idb];
g_pserver->db[idb] = new (MALLOC_LOCAL) redisDb();
g_pserver->db[idb]->initialize(dbid);
}
moduleFireServerEvent(REDISMODULE_EVENT_LOADING, REDISMODULE_SUBEVENT_LOADING_FLASH_START, NULL);
for (int idb = 0; idb < cserver.dbnum; ++idb)
{
g_pserver->db[idb]->storageProviderInitialize();
g_pserver->db[idb]->trackChanges(false);
}
moduleFireServerEvent(REDISMODULE_EVENT_LOADING, REDISMODULE_SUBEVENT_LOADING_ENDED, NULL);
unprotectClient(c);
} else {
/* The default behavior is to save the RDB file before loading
* it back. */
if (save) {
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
if (rdbSave(nullptr, rsiptr) != C_OK) {
addReply(c,shared.err);
return;
}
}
/* The default behavior is to remove the current dataset from
* memory before loading the RDB file, however when MERGE is
* used together with NOFLUSH, we are able to merge two datasets. */
if (flush) emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
protectClient(c);
int ret = rdbLoadFile(g_pserver->rdb_filename,NULL,flags);
unprotectClient(c);
if (ret != C_OK) {
addReplyError(c,"Error trying to load the RDB dump");
return; return;
} }
} }
/* The default behavior is to remove the current dataset from
* memory before loading the RDB file, however when MERGE is
* used together with NOFLUSH, we are able to merge two datasets. */
if (flush) emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
protectClient(c);
int ret = rdbLoadFile(g_pserver->rdb_filename,NULL,flags);
unprotectClient(c);
if (ret != C_OK) {
addReplyError(c,"Error trying to load the RDB dump");
return;
}
serverLog(LL_WARNING,"DB reloaded by DEBUG RELOAD"); serverLog(LL_WARNING,"DB reloaded by DEBUG RELOAD");
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"loadaof")) { } else if (!strcasecmp(szFromObj(c->argv[1]),"loadaof")) {
@@ -716,7 +761,7 @@ NULL
* work on logically expired keys */ * work on logically expired keys */
auto itr = c->db->find(c->argv[j]); auto itr = c->db->find(c->argv[j]);
robj* o = (robj*)(itr == NULL ? NULL : itr.val()); robj* o = (robj*)(itr == NULL ? NULL : itr.val());
if (o) xorObjectDigest(c->db,c->argv[j],digest,o); if (o) xorObjectDigest(digest,o);
sds d = sdsempty(); sds d = sdsempty();
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]); for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
@@ -843,10 +888,6 @@ NULL
g_pserver->db[dbid]->getStats(buf,sizeof(buf)); g_pserver->db[dbid]->getStats(buf,sizeof(buf));
stats = sdscat(stats,buf); stats = sdscat(stats,buf);
stats = sdscatprintf(stats,"[Expires set]\n");
g_pserver->db[dbid]->getExpireStats(buf, sizeof(buf));
stats = sdscat(stats, buf);
addReplyVerbatim(c,stats,sdslen(stats),"txt"); addReplyVerbatim(c,stats,sdslen(stats),"txt");
sdsfree(stats); sdsfree(stats);
} else if (!strcasecmp(szFromObj(c->argv[1]),"htstats-key") && c->argc == 3) { } else if (!strcasecmp(szFromObj(c->argv[1]),"htstats-key") && c->argc == 3) {
@@ -937,6 +978,21 @@ NULL
mallctl_string(c, c->argv+2, c->argc-2); mallctl_string(c, c->argv+2, c->argc-2);
return; return;
#endif #endif
} else if(!strcasecmp(szFromObj(c->argv[1]),"flush-storage") && c->argc == 2) {
if (g_pserver->m_pstorageFactory != nullptr) {
for (int i = 0; i < cserver.dbnum; i++) {
g_pserver->db[i]->getStorageCache()->flush();
}
addReply(c,shared.ok);
} else {
addReplyError(c, "Can't flush storage if no storage provider is set");
}
} else if (!strcasecmp(szFromObj(c->argv[1]),"get-storage-usage") && c->argc == 2) {
if (g_pserver->m_pstorageFactory != nullptr) {
addReplyLongLong(c, g_pserver->m_pstorageFactory->totalDiskspaceUsed());
} else {
addReplyLongLong(c, 0);
}
} else { } else {
addReplySubcommandSyntaxError(c); addReplySubcommandSyntaxError(c);
return; return;
@@ -1079,7 +1135,7 @@ static void *getMcontextEip(ucontext_t *uc) {
#elif defined(__i386__) #elif defined(__i386__)
return (void*) uc->uc_mcontext->__ss.__eip; return (void*) uc->uc_mcontext->__ss.__eip;
#else #else
return (void*) uc->uc_mcontext->__ss.__srr0; return (void*) uc->uc_mcontext->__ss.__pc;
#endif #endif
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) #elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
/* OSX >= 10.6 */ /* OSX >= 10.6 */
+1 -24
View File
@@ -47,7 +47,6 @@ extern "C" int je_get_defrag_hint(void* ptr);
/* forward declarations*/ /* forward declarations*/
void defragDictBucketCallback(void *privdata, dictEntry **bucketref); void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
dictEntry* replaceSatelliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged); dictEntry* replaceSatelliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
bool replaceSatelliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey);
/* Defrag helper for generic allocations. /* Defrag helper for generic allocations.
* *
@@ -425,20 +424,6 @@ dictEntry* replaceSatelliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, s
return NULL; return NULL;
} }
bool replaceSatelliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
auto itr = set.find(oldkey);
if (itr != set.end())
{
expireEntry eNew(std::move(*itr));
eNew.setKeyUnsafe(newkey);
set.erase(itr);
set.insert(eNew);
serverAssert(set.find(newkey) != set.end());
return true;
}
return false;
}
long activeDefragQuickListNode(quicklist *ql, quicklistNode **node_ref) { long activeDefragQuickListNode(quicklist *ql, quicklistNode **node_ref) {
quicklistNode *newnode, *node = *node_ref; quicklistNode *newnode, *node = *node_ref;
long defragged = 0; long defragged = 0;
@@ -851,7 +836,6 @@ long defragModule(redisDb *db, dictEntry *kde) {
* all the various pointers it has. Returns a stat of how many pointers were * all the various pointers it has. Returns a stat of how many pointers were
* moved. */ * moved. */
long defragKey(redisDb *db, dictEntry *de) { long defragKey(redisDb *db, dictEntry *de) {
std::unique_lock<fastlock> ul(g_expireLock);
sds keysds = (sds)dictGetKey(de); sds keysds = (sds)dictGetKey(de);
robj *newob, *ob; robj *newob, *ob;
unsigned char *newzl; unsigned char *newzl;
@@ -862,15 +846,8 @@ long defragKey(redisDb *db, dictEntry *de) {
/* Try to defrag the key name. */ /* Try to defrag the key name. */
newsds = activeDefragSds(keysds); newsds = activeDefragSds(keysds);
if (newsds) if (newsds) {
{
defragged++, de->key = newsds; defragged++, de->key = newsds;
if (!db->setexpire()->empty()) {
bool fReplaced = replaceSatelliteOSetKeyPtr(*const_cast<expireset*>(db->setexpire()), keysds, newsds);
serverAssert(fReplaced == ob->FExpires());
} else {
serverAssert(!ob->FExpires());
}
} }
if ((newob = activeDefragStringOb(ob, &defragged))) { if ((newob = activeDefragStringOb(ob, &defragged))) {
+1 -1
View File
@@ -1577,7 +1577,7 @@ char *stringFromLongLong(long long value) {
int len; int len;
char *s; char *s;
len = snprintf(buf,32,"%lld",value); len = snprintf(buf,sizeof(buf),"%lld",value);
s = zmalloc(len+1); s = zmalloc(len+1);
memcpy(s, buf, len); memcpy(s, buf, len);
s[len] = '\0'; s[len] = '\0';
+3 -3
View File
@@ -112,15 +112,15 @@ int endianconvTest(int argc, char *argv[], int accurate) {
UNUSED(argv); UNUSED(argv);
UNUSED(accurate); UNUSED(accurate);
snprintf(buf,32,"ciaoroma"); snprintf(buf,sizeof(buf),"ciaoroma");
memrev16(buf); memrev16(buf);
printf("%s\n", buf); printf("%s\n", buf);
snprintf(buf,32,"ciaoroma"); snprintf(buf,sizeof(buf),"ciaoroma");
memrev32(buf); memrev32(buf);
printf("%s\n", buf); printf("%s\n", buf);
snprintf(buf,32,"ciaoroma"); snprintf(buf,sizeof(buf),"ciaoroma");
memrev64(buf); memrev64(buf);
printf("%s\n", buf); printf("%s\n", buf);
+159 -94
View File
@@ -100,6 +100,36 @@ unsigned long long estimateObjectIdleTime(robj_roptr o) {
} }
} }
unsigned long long getIdle(robj *obj, const expireEntry *e) {
unsigned long long idle;
/* Calculate the idle time according to the policy. This is called
* idle just because the code initially handled LRU, but is in fact
* just a score where an higher score means better candidate. */
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LRU) {
idle = (obj != nullptr) ? estimateObjectIdleTime(obj) : 0;
} else if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
/* When we use an LRU policy, we sort the keys by idle time
* so that we expire keys starting from greater idle time.
* However when the policy is an LFU one, we have a frequency
* estimation, and we want to evict keys with lower frequency
* first. So inside the pool we put objects using the inverted
* frequency subtracting the actual frequency to the maximum
* frequency of 255. */
idle = 255-LFUDecrAndReturn(obj);
} else if (g_pserver->maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
/* In this case the sooner the expire the better. */
if (e != nullptr)
idle = ULLONG_MAX - e->when();
else
idle = 0;
} else if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
idle = ULLONG_MAX;
} else {
serverPanic("Unknown eviction policy in storage eviction");
}
return idle;
}
/* LRU approximation algorithm /* LRU approximation algorithm
* *
* Redis uses an approximation of the LRU algorithm that runs in constant * Redis uses an approximation of the LRU algorithm that runs in constant
@@ -137,28 +167,7 @@ void evictionPoolAlloc(void) {
void processEvictionCandidate(int dbid, sds key, robj *o, const expireEntry *e, struct evictionPoolEntry *pool) void processEvictionCandidate(int dbid, sds key, robj *o, const expireEntry *e, struct evictionPoolEntry *pool)
{ {
unsigned long long idle; unsigned long long idle = getIdle(o,e);
/* Calculate the idle time according to the policy. This is called
* idle just because the code initially handled LRU, but is in fact
* just a score where an higher score means better candidate. */
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LRU) {
idle = (o != nullptr) ? estimateObjectIdleTime(o) : 0;
} else if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
/* When we use an LRU policy, we sort the keys by idle time
* so that we expire keys starting from greater idle time.
* However when the policy is an LFU one, we have a frequency
* estimation, and we want to evict keys with lower frequency
* first. So inside the pool we put objects using the inverted
* frequency subtracting the actual frequency to the maximum
* frequency of 255. */
idle = 255-LFUDecrAndReturn(o);
} else if (g_pserver->maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
/* In this case the sooner the expire the better. */
idle = ULLONG_MAX - e->when();
} else {
serverPanic("Unknown eviction policy in evictionPoolPopulate()");
}
/* Insert the element inside the pool. /* Insert the element inside the pool.
* First, find the first empty bucket or the first populated * First, find the first empty bucket or the first populated
@@ -222,52 +231,23 @@ void processEvictionCandidate(int dbid, sds key, robj *o, const expireEntry *e,
* idle time are on the left, and keys with the higher idle time on the * idle time are on the left, and keys with the higher idle time on the
* right. */ * right. */
struct visitFunctor int evictionPoolPopulate(int dbid, redisDb *db, bool fVolatile, struct evictionPoolEntry *pool)
{ {
int dbid; int returnCount = 0;
dict *dbdict; dictEntry **samples = (dictEntry**)alloca(g_pserver->maxmemory_samples * sizeof(dictEntry*));
struct evictionPoolEntry *pool; int count = dictGetSomeKeys(db->dictUnsafeKeyOnly(),samples,g_pserver->maxmemory_samples);
int count = 0; for (int j = 0; j < count; j++) {
int tries = 0; robj *o = (robj*)dictGetVal(samples[j]);
// If the object is in second tier storage we don't need to evict it (since it already is)
bool operator()(const expireEntry &e) if (o != nullptr)
{
dictEntry *de = dictFind(dbdict, e.key());
if (de != nullptr)
{ {
processEvictionCandidate(dbid, (sds)dictGetKey(de), (robj*)dictGetVal(de), &e, pool); if (!fVolatile || o->FExpires()) {
++count; processEvictionCandidate(dbid, (sds)dictGetKey(samples[j]), o, &o->expire, pool);
}
++tries;
return tries < g_pserver->maxmemory_samples;
}
};
int evictionPoolPopulate(int dbid, redisDb *db, expireset *setexpire, struct evictionPoolEntry *pool)
{
if (setexpire != nullptr)
{
std::unique_lock<fastlock> ul(g_expireLock);
visitFunctor visitor { dbid, db->dictUnsafeKeyOnly(), pool, 0 };
setexpire->random_visit(visitor);
return visitor.count;
}
else
{
int returnCount = 0;
dictEntry **samples = (dictEntry**)alloca(g_pserver->maxmemory_samples * sizeof(dictEntry*));
int count = dictGetSomeKeys(db->dictUnsafeKeyOnly(),samples,g_pserver->maxmemory_samples);
for (int j = 0; j < count; j++) {
robj *o = (robj*)dictGetVal(samples[j]);
// If the object is in second tier storage we don't need to evict it (since it alrady is)
if (o != nullptr)
{
processEvictionCandidate(dbid, (sds)dictGetKey(samples[j]), o, nullptr, pool);
++returnCount; ++returnCount;
} }
} }
return returnCount;
} }
return 0; return returnCount;
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@@ -380,7 +360,7 @@ size_t freeMemoryGetNotCountedMemory(void) {
/* also don't count the replication backlog memory /* also don't count the replication backlog memory
* that's where the replication clients get their memory from */ * that's where the replication clients get their memory from */
overhead += g_pserver->repl_backlog_size; overhead += g_pserver->repl_backlog_size - g_pserver->repl_backlog_config_size;
if (g_pserver->aof_state != AOF_OFF) { if (g_pserver->aof_state != AOF_OFF) {
overhead += sdsalloc(g_pserver->aof_buf)+aofRewriteBufferSize(); overhead += sdsalloc(g_pserver->aof_buf)+aofRewriteBufferSize();
@@ -411,8 +391,13 @@ size_t freeMemoryGetNotCountedMemory(void) {
* memory currently used. May be > 1 if we are over the memory * memory currently used. May be > 1 if we are over the memory
* limit. * limit.
* (Populated both for C_ERR and C_OK) * (Populated both for C_ERR and C_OK)
*
* 'reason' the reason why the memory limit was exceeded
* EVICT_REASON_USER: reported user memory exceeded maxmemory
* EVICT_REASON_SYS: available system memory under configurable threshold
* (Populated when C_ERR is returned)
*/ */
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level, bool fQuickCycle, bool fPreSnapshot) { int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level, EvictReason *reason, bool fQuickCycle, bool fPreSnapshot) {
size_t mem_reported, mem_used, mem_tofree; size_t mem_reported, mem_used, mem_tofree;
/* Check if we are over the memory usage limit. If we are not, no need /* Check if we are over the memory usage limit. If we are not, no need
@@ -421,10 +406,22 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
if (total) *total = mem_reported; if (total) *total = mem_reported;
size_t maxmemory = g_pserver->maxmemory; size_t maxmemory = g_pserver->maxmemory;
if (fPreSnapshot) if (fPreSnapshot)
maxmemory = static_cast<size_t>(maxmemory * 0.9); // derate memory by 10% since we won't be able to free during snapshot maxmemory = static_cast<size_t>(maxmemory*0.9); // derate memory by 10% since we won't be able to free during snapshot
if (g_pserver->FRdbSaveInProgress()) if (g_pserver->FRdbSaveInProgress() && !cserver.fForkBgSave)
maxmemory = static_cast<size_t>(maxmemory*1.2); maxmemory = static_cast<size_t>(maxmemory*1.2);
/* If available system memory is below a certain threshold, force eviction */
long long sys_available_mem_buffer = 0;
if (g_pserver->force_eviction_percent && g_pserver->cron_malloc_stats.sys_total) {
float available_mem_ratio = (float)(100 - g_pserver->force_eviction_percent)/100;
size_t min_available_mem = static_cast<size_t>(g_pserver->cron_malloc_stats.sys_total * available_mem_ratio);
sys_available_mem_buffer = static_cast<long>(g_pserver->cron_malloc_stats.sys_available - min_available_mem);
if (sys_available_mem_buffer < 0) {
long long mem_threshold = mem_reported + sys_available_mem_buffer;
maxmemory = ((long long)maxmemory < mem_threshold) ? maxmemory : static_cast<size_t>(mem_threshold);
}
}
/* We may return ASAP if there is no need to compute the level. */ /* We may return ASAP if there is no need to compute the level. */
int return_ok_asap = !maxmemory || mem_reported <= maxmemory; int return_ok_asap = !maxmemory || mem_reported <= maxmemory;
if (return_ok_asap && !level) return C_OK; if (return_ok_asap && !level) return C_OK;
@@ -435,6 +432,12 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
size_t overhead = freeMemoryGetNotCountedMemory(); size_t overhead = freeMemoryGetNotCountedMemory();
mem_used = (mem_used > overhead) ? mem_used-overhead : 0; mem_used = (mem_used > overhead) ? mem_used-overhead : 0;
/* If system available memory is too low, we want to force evictions no matter
* what so we also offset the overhead from maxmemory. */
if (sys_available_mem_buffer < 0) {
maxmemory = (maxmemory > overhead) ? maxmemory-overhead : 0;
}
/* Compute the ratio of memory usage. */ /* Compute the ratio of memory usage. */
if (level) { if (level) {
if (!maxmemory) { if (!maxmemory) {
@@ -459,6 +462,8 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
if (logical) *logical = mem_used; if (logical) *logical = mem_used;
if (tofree) *tofree = mem_tofree; if (tofree) *tofree = mem_tofree;
if (reason) *reason = sys_available_mem_buffer < 0 ? EvictReason::System : EvictReason::User;
return C_ERR; return C_ERR;
} }
@@ -483,6 +488,7 @@ public:
for (auto de : pair.second) { for (auto de : pair.second) {
dictFreeUnlinkedEntry(pair.first, de); dictFreeUnlinkedEntry(pair.first, de);
} }
dictRelease(pair.first);
} }
aeReleaseLock(); aeReleaseLock();
--s_clazyFreesInProgress; --s_clazyFreesInProgress;
@@ -513,6 +519,7 @@ public:
); );
if (itr == vecdictvecde.end() || itr->first != d) { if (itr == vecdictvecde.end() || itr->first != d) {
itr = vecdictvecde.insert(itr, std::make_pair(d, std::vector<dictEntry*>())); itr = vecdictvecde.insert(itr, std::make_pair(d, std::vector<dictEntry*>()));
__atomic_fetch_add(&d->refcount, 1, __ATOMIC_ACQ_REL);
} }
serverAssert(itr->first == d); serverAssert(itr->first == d);
itr->second.push_back(de); itr->second.push_back(de);
@@ -552,7 +559,7 @@ static int evictionTimeProc(
UNUSED(clientData); UNUSED(clientData);
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
if (performEvictions(false) == EVICT_RUNNING) return 0; /* keep evicting */ if (performEvictions((bool)clientData) == EVICT_RUNNING) return 0; /* keep evicting */
/* For EVICT_OK - things are good, no need to keep evicting. /* For EVICT_OK - things are good, no need to keep evicting.
* For EVICT_FAIL - there is nothing left to evict. */ * For EVICT_FAIL - there is nothing left to evict. */
@@ -602,6 +609,37 @@ static unsigned long evictionTimeLimitUs() {
return ULONG_MAX; /* No limit to eviction time */ return ULONG_MAX; /* No limit to eviction time */
} }
void evict(redisDb *db, robj *keyobj) {
mstime_t eviction_latency;
propagateExpire(db,keyobj,g_pserver->lazyfree_lazy_eviction);
/* We compute the amount of memory freed by db*Delete() alone.
* It is possible that actually the memory needed to propagate
* the DEL in AOF and replication link is greater than the one
* we are freeing removing the key, but we can't account for
* that otherwise we would never exit the loop.
*
* AOF and Output buffer memory will be freed eventually so
* we only care about memory used by the key space. */
latencyStartMonitor(eviction_latency);
if (g_pserver->lazyfree_lazy_eviction)
dbAsyncDelete(db,keyobj);
else
dbSyncDelete(db,keyobj);
latencyEndMonitor(eviction_latency);
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
signalModifiedKey(NULL,db,keyobj);
notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
keyobj, db->id);
decrRefCount(keyobj);
}
static void updateSysAvailableMemory() {
if (g_pserver->force_eviction_percent) {
g_pserver->cron_malloc_stats.sys_available = getMemAvailable();
}
}
/* Check that memory usage is within the current "maxmemory" limit. If over /* Check that memory usage is within the current "maxmemory" limit. If over
* "maxmemory", attempt to free memory by evicting data (if it's safe to do so). * "maxmemory", attempt to free memory by evicting data (if it's safe to do so).
* *
@@ -633,16 +671,17 @@ int performEvictions(bool fPreSnapshot) {
int keys_freed = 0; int keys_freed = 0;
size_t mem_reported, mem_tofree; size_t mem_reported, mem_tofree;
long long mem_freed; /* May be negative */ long long mem_freed; /* May be negative */
mstime_t latency, eviction_latency; mstime_t latency;
long long delta; long long delta;
int slaves = listLength(g_pserver->slaves); int slaves = listLength(g_pserver->slaves);
const bool fEvictToStorage = !cserver.delete_on_evict && g_pserver->db[0]->FStorageProvider(); const bool fEvictToStorage = !cserver.delete_on_evict && g_pserver->db[0]->FStorageProvider();
int result = EVICT_FAIL; int result = EVICT_FAIL;
int ckeysFailed = 0; int ckeysFailed = 0;
EvictReason evictReason;
std::unique_ptr<FreeMemoryLazyFree> splazy = std::make_unique<FreeMemoryLazyFree>(); std::unique_ptr<FreeMemoryLazyFree> splazy = std::make_unique<FreeMemoryLazyFree>();
if (getMaxmemoryState(&mem_reported,NULL,&mem_tofree,NULL,false,fPreSnapshot) == C_OK) if (getMaxmemoryState(&mem_reported,NULL,&mem_tofree,NULL,&evictReason,false,fPreSnapshot) == C_OK)
return EVICT_OK; return EVICT_OK;
if (g_pserver->maxmemory_policy == MAXMEMORY_NO_EVICTION) if (g_pserver->maxmemory_policy == MAXMEMORY_NO_EVICTION)
@@ -657,6 +696,43 @@ int performEvictions(bool fPreSnapshot) {
monotime evictionTimer; monotime evictionTimer;
elapsedStart(&evictionTimer); elapsedStart(&evictionTimer);
if (g_pserver->maxstorage && g_pserver->m_pstorageFactory != nullptr) {
while (g_pserver->m_pstorageFactory->totalDiskspaceUsed() >= g_pserver->maxstorage && elapsedUs(evictionTimer) < eviction_time_limit_us) {
redisDb *db;
std::vector<std::string> evictionPool;
robj *bestkey = nullptr;
redisDb *bestdb = nullptr;
unsigned long long bestidle = 0;
for (int i = 0; i < cserver.dbnum; i++) {
db = g_pserver->db[i];
evictionPool = db->getStorageCache()->getEvictionCandidates(g_pserver->maxmemory_samples);
for (std::string key : evictionPool) {
robj *keyobj = createStringObject(key.c_str(), key.size());
robj *obj = db->find(szFromObj(keyobj));
if (obj != nullptr) {
expireEntry *e = db->getExpire(keyobj);
unsigned long long idle = getIdle(obj, e);
if (bestkey == nullptr || bestidle < idle) {
if (bestkey != nullptr)
decrRefCount(bestkey);
incrRefCount(keyobj);
bestkey = keyobj;
bestidle = idle;
bestdb = db;
}
}
decrRefCount(keyobj);
}
}
if (bestkey) {
evict(bestdb, bestkey);
} else {
break; //could not find a key to evict so stop now
}
}
}
if (g_pserver->maxstorage && g_pserver->m_pstorageFactory != nullptr && g_pserver->m_pstorageFactory->totalDiskspaceUsed() >= g_pserver->maxstorage) if (g_pserver->maxstorage && g_pserver->m_pstorageFactory != nullptr && g_pserver->m_pstorageFactory->totalDiskspaceUsed() >= g_pserver->maxstorage)
goto cant_free_storage; goto cant_free_storage;
@@ -684,14 +760,14 @@ int performEvictions(bool fPreSnapshot) {
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS)
{ {
if ((keys = db->size()) != 0) { if ((keys = db->size()) != 0) {
total_keys += evictionPoolPopulate(i, db, nullptr, pool); total_keys += evictionPoolPopulate(i, db, false, pool);
} }
} }
else else
{ {
keys = db->expireSize(); keys = db->expireSize();
if (keys != 0) if (keys != 0)
total_keys += evictionPoolPopulate(i, db, db->setexpireUnsafe(), pool); total_keys += evictionPoolPopulate(i, db, true, pool);
} }
} }
if (!total_keys) break; /* No keys to evict. */ if (!total_keys) break; /* No keys to evict. */
@@ -752,7 +828,7 @@ int performEvictions(bool fPreSnapshot) {
{ {
if (db->expireSize()) if (db->expireSize())
{ {
bestkey = (sds)db->random_expire().key(); db->random_expire(&bestkey);
bestdbid = j; bestdbid = j;
break; break;
} }
@@ -771,6 +847,7 @@ int performEvictions(bool fPreSnapshot) {
if (db->removeCachedValue(bestkey, &deT)) { if (db->removeCachedValue(bestkey, &deT)) {
mem_freed += splazy->addEntry(db->dictUnsafeKeyOnly(), deT); mem_freed += splazy->addEntry(db->dictUnsafeKeyOnly(), deT);
ckeysFailed = 0; ckeysFailed = 0;
g_pserver->stat_evictedkeys++;
} }
else { else {
delta = 0; delta = 0;
@@ -782,30 +859,11 @@ int performEvictions(bool fPreSnapshot) {
else else
{ {
robj *keyobj = createStringObject(bestkey,sdslen(bestkey)); robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
propagateExpire(db,keyobj,g_pserver->lazyfree_lazy_eviction);
/* We compute the amount of memory freed by db*Delete() alone.
* It is possible that actually the memory needed to propagate
* the DEL in AOF and replication link is greater than the one
* we are freeing removing the key, but we can't account for
* that otherwise we would never exit the loop.
*
* AOF and Output buffer memory will be freed eventually so
* we only care about memory used by the key space. */
delta = (long long) zmalloc_used_memory(); delta = (long long) zmalloc_used_memory();
latencyStartMonitor(eviction_latency); evict(db, keyobj);
if (g_pserver->lazyfree_lazy_eviction)
dbAsyncDelete(db,keyobj);
else
dbSyncDelete(db,keyobj);
latencyEndMonitor(eviction_latency);
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
delta -= (long long) zmalloc_used_memory(); delta -= (long long) zmalloc_used_memory();
mem_freed += delta; mem_freed += delta;
g_pserver->stat_evictedkeys++; g_pserver->stat_evictedkeys++;
signalModifiedKey(NULL,db,keyobj);
notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
keyobj, db->id);
decrRefCount(keyobj);
} }
keys_freed++; keys_freed++;
@@ -824,6 +882,9 @@ int performEvictions(bool fPreSnapshot) {
* across the dbAsyncDelete() call, while the thread can * across the dbAsyncDelete() call, while the thread can
* release the memory all the time. */ * release the memory all the time. */
if (g_pserver->lazyfree_lazy_eviction) { if (g_pserver->lazyfree_lazy_eviction) {
if (evictReason == EvictReason::System) {
updateSysAvailableMemory();
}
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) { if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
break; break;
} }
@@ -837,7 +898,7 @@ int performEvictions(bool fPreSnapshot) {
if (!isEvictionProcRunning && serverTL->el != nullptr) { if (!isEvictionProcRunning && serverTL->el != nullptr) {
isEvictionProcRunning = 1; isEvictionProcRunning = 1;
aeCreateTimeEvent(serverTL->el, 0, aeCreateTimeEvent(serverTL->el, 0,
evictionTimeProc, NULL, NULL); evictionTimeProc, (void*)fPreSnapshot, NULL);
} }
break; break;
} }
@@ -851,9 +912,13 @@ int performEvictions(bool fPreSnapshot) {
if (splazy != nullptr && splazy->memory_queued() > 0 && !serverTL->gcEpoch.isReset()) { if (splazy != nullptr && splazy->memory_queued() > 0 && !serverTL->gcEpoch.isReset()) {
g_pserver->garbageCollector.enqueue(serverTL->gcEpoch, std::move(splazy)); g_pserver->garbageCollector.enqueue(serverTL->gcEpoch, std::move(splazy));
} }
cant_free: cant_free:
if (mem_freed > 0 && evictReason == EvictReason::System) {
updateSysAvailableMemory();
}
if (g_pserver->m_pstorageFactory) if (g_pserver->m_pstorageFactory)
{ {
if (mem_reported < g_pserver->maxmemory*1.2) { if (mem_reported < g_pserver->maxmemory*1.2) {
+192 -79
View File
@@ -33,8 +33,6 @@
#include "server.h" #include "server.h"
#include "cron.h" #include "cron.h"
fastlock g_expireLock {"Expire"};
/* Helper function for the activeExpireCycle() function. /* Helper function for the activeExpireCycle() function.
* This function will try to expire the key that is stored in the hash table * This function will try to expire the key that is stored in the hash table
* entry 'de' of the 'expires' hash table of a Redis database. * entry 'de' of the 'expires' hash table of a Redis database.
@@ -74,21 +72,20 @@ void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
int activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now, size_t &tried) { int activeExpireCycleExpire(redisDb *db, const char *key, expireEntry &e, long long now, size_t &tried) {
if (!e.FFat()) if (!e.FFat())
{ {
activeExpireCycleExpireFullKey(db, e.key()); activeExpireCycleExpireFullKey(db, key);
++tried; ++tried;
return 1; return 1;
} }
expireEntryFat *pfat = e.pfatentry(); expireEntryFat *pfat = e.pfatentry();
robj *val = db->find(e.key()); robj *val = db->find(key);
int deleted = 0; int deleted = 0;
redisObjectStack objKey; redisObjectStack objKey;
initStaticStringObject(objKey, (char*)e.key()); initStaticStringObject(objKey, (char*)key);
bool fTtlChanged = false;
while (!pfat->FEmpty()) while (!pfat->FEmpty())
{ {
@@ -99,7 +96,7 @@ int activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now, size_t &
// Is it the full key expiration? // Is it the full key expiration?
if (pfat->nextExpireEntry().spsubkey == nullptr) if (pfat->nextExpireEntry().spsubkey == nullptr)
{ {
activeExpireCycleExpireFullKey(db, e.key()); activeExpireCycleExpireFullKey(db, key);
return ++deleted; return ++deleted;
} }
@@ -109,7 +106,7 @@ int activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now, size_t &
if (setTypeRemove(val,pfat->nextExpireEntry().spsubkey.get())) { if (setTypeRemove(val,pfat->nextExpireEntry().spsubkey.get())) {
deleted++; deleted++;
if (setTypeSize(val) == 0) { if (setTypeSize(val) == 0) {
activeExpireCycleExpireFullKey(db, e.key()); activeExpireCycleExpireFullKey(db, key);
return deleted; return deleted;
} }
} }
@@ -119,7 +116,7 @@ int activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now, size_t &
if (hashTypeDelete(val,(sds)pfat->nextExpireEntry().spsubkey.get())) { if (hashTypeDelete(val,(sds)pfat->nextExpireEntry().spsubkey.get())) {
deleted++; deleted++;
if (hashTypeLength(val) == 0) { if (hashTypeLength(val) == 0) {
activeExpireCycleExpireFullKey(db, e.key()); activeExpireCycleExpireFullKey(db, key);
return deleted; return deleted;
} }
} }
@@ -129,7 +126,7 @@ int activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now, size_t &
if (zsetDel(val,(sds)pfat->nextExpireEntry().spsubkey.get())) { if (zsetDel(val,(sds)pfat->nextExpireEntry().spsubkey.get())) {
deleted++; deleted++;
if (zsetLength(val) == 0) { if (zsetLength(val) == 0) {
activeExpireCycleExpireFullKey(db, e.key()); activeExpireCycleExpireFullKey(db, key);
return deleted; return deleted;
} }
} }
@@ -137,15 +134,15 @@ int activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now, size_t &
case OBJ_CRON: case OBJ_CRON:
{ {
sds keyCopy = sdsdup(e.key()); sds keyCopy = sdsdup(key);
incrRefCount(val); incrRefCount(val);
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [keyCopy, val]{ aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [keyCopy, val]{
executeCronJobExpireHook(keyCopy, val); executeCronJobExpireHook(keyCopy, val);
sdsfree(keyCopy); sdsfree(keyCopy);
decrRefCount(val); decrRefCount(val);
}, true /*fLock*/, true /*fForceQueue*/); }, true /*fLock*/, true /*fForceQueue*/);
break;
} }
return deleted;
case OBJ_LIST: case OBJ_LIST:
default: default:
@@ -157,7 +154,6 @@ int activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now, size_t &
propagateSubkeyExpire(db, val->type, &objKey, &objSubkey); propagateSubkeyExpire(db, val->type, &objKey, &objSubkey);
pfat->popfrontExpireEntry(); pfat->popfrontExpireEntry();
fTtlChanged = true;
if ((tried % ACTIVE_EXPIRE_CYCLE_SUBKEY_LOOKUPS_PER_LOOP) == 0) { if ((tried % ACTIVE_EXPIRE_CYCLE_SUBKEY_LOOKUPS_PER_LOOP) == 0) {
break; break;
} }
@@ -167,11 +163,6 @@ int activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now, size_t &
{ {
removeExpire(db, &objKey); removeExpire(db, &objKey);
} }
else if (!pfat->FEmpty() && fTtlChanged)
{
// We need to resort the expire entry since it may no longer be in the correct position
db->resortExpire(e);
}
if (deleted) if (deleted)
{ {
@@ -317,8 +308,26 @@ void pexpireMemberAtCommand(client *c)
* If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is * If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
* executed, where the time limit is a percentage of the REDIS_HZ period * executed, where the time limit is a percentage of the REDIS_HZ period
* as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. */ * as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. */
#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds. */
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
#define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
we do extra efforts. */
/*static*/ void redisDbPersistentData::activeExpireCycleCore(int type) {
/* Adjust the running parameters according to the configured expire
* effort. The default effort is 1, and the maximum configurable effort
* is 10. */
unsigned long
effort = g_pserver->active_expire_effort-1, /* Rescale from 0 to 9. */
config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION +
ACTIVE_EXPIRE_CYCLE_FAST_DURATION/4*effort,
config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +
2*effort,
config_cycle_acceptable_stale = ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE-
effort;
void activeExpireCycleCore(int type) {
/* This function has some global state in order to continue the work /* This function has some global state in order to continue the work
* incrementally across calls. */ * incrementally across calls. */
static unsigned int current_db = 0; /* Next DB to test. */ static unsigned int current_db = 0; /* Next DB to test. */
@@ -336,10 +345,16 @@ void activeExpireCycleCore(int type) {
if (type == ACTIVE_EXPIRE_CYCLE_FAST) { if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
/* Don't start a fast cycle if the previous cycle did not exit /* Don't start a fast cycle if the previous cycle did not exit
* for time limit. Also don't repeat a fast cycle for the same period * for time limit, unless the percentage of estimated stale keys is
* too high. Also never repeat a fast cycle for the same period
* as the fast cycle total duration itself. */ * as the fast cycle total duration itself. */
if (!timelimit_exit) return; if (!timelimit_exit &&
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return; g_pserver->stat_expired_stale_perc < config_cycle_acceptable_stale)
return;
if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
return;
last_fast_cycle = start; last_fast_cycle = start;
} }
@@ -353,16 +368,16 @@ void activeExpireCycleCore(int type) {
if (dbs_per_call > cserver.dbnum || timelimit_exit) if (dbs_per_call > cserver.dbnum || timelimit_exit)
dbs_per_call = cserver.dbnum; dbs_per_call = cserver.dbnum;
/* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time /* We can use at max 'config_cycle_slow_time_perc' percentage of CPU
* per iteration. Since this function gets called with a frequency of * time per iteration. Since this function gets called with a frequency of
* g_pserver->hz times per second, the following is the max amount of * server.hz times per second, the following is the max amount of
* microseconds we can spend in this function. */ * microseconds we can spend in this function. */
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/g_pserver->hz/100; timelimit = config_cycle_slow_time_perc*1000000/g_pserver->hz/100;
timelimit_exit = 0; timelimit_exit = 0;
if (timelimit <= 0) timelimit = 1; if (timelimit <= 0) timelimit = 1;
if (type == ACTIVE_EXPIRE_CYCLE_FAST) if (type == ACTIVE_EXPIRE_CYCLE_FAST)
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */ timelimit = config_cycle_fast_duration; /* in microseconds. */
/* Accumulate some global stats as we expire keys, to have some idea /* Accumulate some global stats as we expire keys, to have some idea
* about the number of keys that are already logically expired, but still * about the number of keys that are already logically expired, but still
@@ -371,6 +386,9 @@ void activeExpireCycleCore(int type) {
long total_expired = 0; long total_expired = 0;
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) { for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
/* Expired and checked in a single loop. */
unsigned long expired, sampled;
redisDb *db = g_pserver->db[(current_db % cserver.dbnum)]; redisDb *db = g_pserver->db[(current_db % cserver.dbnum)];
/* Increment the DB now so we are sure if we run out of time /* Increment the DB now so we are sure if we run out of time
@@ -378,48 +396,156 @@ void activeExpireCycleCore(int type) {
* distribute the time evenly across DBs. */ * distribute the time evenly across DBs. */
current_db++; current_db++;
long long now; if (g_pserver->m_pstorageFactory == nullptr) {
iteration++; /* Continue to expire if at the end of the cycle there are still
now = mstime(); * a big percentage of keys to expire, compared to the number of keys
* we scanned. The percentage, stored in config_cycle_acceptable_stale
* is not fixed, but depends on the Redis configured "expire effort". */
do {
unsigned long num, slots;
long long now, ttl_sum;
int ttl_samples;
iteration++;
/* If there is nothing to expire try next DB ASAP. */ /* If there is nothing to expire try next DB ASAP. */
if (db->setexpireUnsafe()->empty()) if (db->expireSize() == 0) {
{ db->avg_ttl = 0;
db->avg_ttl = 0; break;
db->last_expire_set = now; }
continue; num = dictSize(db->m_pdict);
} slots = dictSlots(db->m_pdict);
now = mstime();
std::unique_lock<fastlock> ul(g_expireLock);
size_t expired = 0; /* When there are less than 1% filled slots, sampling the key
size_t tried = 0; * space is expensive, so stop here waiting for better times...
long long check = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; // assume a check is roughly 1us. It isn't but good enough * The dictionary will be resized asap. */
db->expireitr = db->setexpireUnsafe()->enumerate(db->expireitr, now, [&](expireEntry &e) __attribute__((always_inline)) { if (slots > DICT_HT_INITIAL_SIZE &&
if (e.when() < now) (num*100/slots < 1)) break;
{
expired += activeExpireCycleExpire(db, e, now, tried); /* The main collection cycle. Sample random keys among keys
} * with an expire set, checking for expired ones. */
expired = 0;
sampled = 0;
ttl_sum = 0;
ttl_samples = 0;
if (num > config_keys_per_loop)
num = config_keys_per_loop;
/* Here we access the low level representation of the hash table
* for speed concerns: this makes this code coupled with dict.c,
* but it hardly changed in ten years.
*
* Note that certain places of the hash table may be empty,
* so we want also a stop condition about the number of
* buckets that we scanned. However scanning for free buckets
* is very fast: we are in the cache line scanning a sequential
* array of NULL pointers, so we can scan a lot more buckets
* than keys in the same time. */
long max_buckets = num*20;
long checked_buckets = 0;
while (sampled < num && checked_buckets < max_buckets) {
for (int table = 0; table < 2; table++) {
if (table == 1 && !dictIsRehashing(db->m_pdict)) break;
unsigned long idx = db->expires_cursor;
idx &= db->m_pdict->ht[table].sizemask;
dictEntry *de = db->m_pdict->ht[table].table[idx];
long long ttl;
/* Scan the current bucket of the current table. */
checked_buckets++;
while(de) {
/* Get the next entry now since this entry may get
* deleted. */
dictEntry *e = de;
robj *o = (robj*)dictGetVal(de);
de = de->next;
if (!o->FExpires())
continue;
expireEntry *exp = &o->expire;
serverAssert(exp->when() > 0);
ttl = exp->when()-now;
size_t tried = 0;
if (exp->when() <= now) {
if (activeExpireCycleExpire(db,(const char*)dictGetKey(e),*exp,now,tried)) expired++;
serverAssert(ttl <= 0);
} else {
serverAssert(ttl > 0);
}
if (ttl > 0) {
/* We want the average TTL of keys yet
* not expired. */
ttl_sum += ttl;
ttl_samples++;
}
sampled++;
}
}
db->expires_cursor++;
}
total_expired += expired;
total_sampled += sampled;
/* Update the average TTL stats for this database. */
if (ttl_samples) {
long long avg_ttl = ttl_sum/ttl_samples;
/* Do a simple running average with a few samples.
* We just use the current estimate with a weight of 2%
* and the previous estimate with a weight of 98%. */
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
}
if ((tried % ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP) == 0)
{
/* We can't block forever here even if there are many keys to /* We can't block forever here even if there are many keys to
* expire. So after a given amount of milliseconds return to the * expire. So after a given amount of milliseconds return to the
* caller waiting for the other active expire cycle. */ * caller waiting for the other active expire cycle. */
elapsed = ustime()-start; if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
if (elapsed > timelimit) { elapsed = ustime()-start;
timelimit_exit = 1; if (elapsed > timelimit) {
g_pserver->stat_expired_time_cap_reached_count++; timelimit_exit = 1;
return false; g_pserver->stat_expired_time_cap_reached_count++;
break;
}
} }
check = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* We don't repeat the cycle for the current database if there are
} * an acceptable amount of stale keys (logically expired but yet
return true; * not reclaimed). */
}, &check); } while (sampled == 0 ||
(expired*100/sampled) > config_cycle_acceptable_stale);
} else {
long prev_expired;
long long now = mstime();
size_t tried = 0;
std::vector<std::string> keys;
do {
prev_expired = total_expired;
keys = db->getStorageCache()->getExpirationCandidates(ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP);
for (std::string key : keys) {
robj* keyobj = createStringObject(key.c_str(), key.size());
db->find(szFromObj(keyobj));
expireEntry *e = db->getExpire(keyobj);
if (e != nullptr && e->when() < now)
total_expired += activeExpireCycleExpire(db, szFromObj(keyobj), *e, now, tried);
decrRefCount(keyobj);
}
total_sampled += keys.size();
elapsed = ustime()-start;
} while (keys.size() > 0 && (elapsed < timelimit) && (total_expired - prev_expired) > 0);
total_expired += expired; if (ustime()-start > timelimit) {
timelimit_exit = 1;
g_pserver->stat_expired_time_cap_reached_count++;
}
}
} }
elapsed = ustime()-start; elapsed = ustime()-start;
g_pserver->stat_expire_cycle_time_used += elapsed;
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000); latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
/* Update our estimate of keys existing but yet to be expired. /* Update our estimate of keys existing but yet to be expired.
@@ -435,7 +561,7 @@ void activeExpireCycleCore(int type) {
void activeExpireCycle(int type) void activeExpireCycle(int type)
{ {
runAndPropogateToReplicas(activeExpireCycleCore, type); runAndPropogateToReplicas(redisDbPersistentData::activeExpireCycleCore, type);
} }
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
@@ -495,19 +621,14 @@ void expireSlaveKeys(void) {
while(dbids && dbid < cserver.dbnum) { while(dbids && dbid < cserver.dbnum) {
if ((dbids & 1) != 0) { if ((dbids & 1) != 0) {
redisDb *db = g_pserver->db[dbid]; redisDb *db = g_pserver->db[dbid];
// the expire is hashed based on the key pointer, so we need the point in the main db
auto itrDB = db->find(keyname); auto itrDB = db->find(keyname);
auto itrExpire = db->setexpire()->end();
if (itrDB != nullptr)
itrExpire = db->setexpireUnsafe()->find(itrDB.key());
int expired = 0; int expired = 0;
if (itrExpire != db->setexpire()->end()) if (itrDB != db->end() && itrDB->FExpires())
{ {
if (itrExpire->when() < start) { if (itrDB->expire.when() < start) {
size_t tried = 0; size_t tried = 0;
expired = activeExpireCycleExpire(g_pserver->db[dbid],*itrExpire,start,tried); expired = activeExpireCycleExpire(g_pserver->db[dbid],itrDB.key(),itrDB->expire,start,tried);
} }
} }
@@ -515,7 +636,7 @@ void expireSlaveKeys(void) {
* corresponding bit in the new bitmap we set as value. * corresponding bit in the new bitmap we set as value.
* At the end of the loop if the bitmap is zero, it means we * At the end of the loop if the bitmap is zero, it means we
* no longer need to keep track of this key. */ * no longer need to keep track of this key. */
if (itrExpire != db->setexpire()->end() && !expired) { if (itrDB != db->end() && itrDB->FExpires() && !expired) {
noexpire++; noexpire++;
new_dbids |= (uint64_t)1 << dbid; new_dbids |= (uint64_t)1 << dbid;
} }
@@ -693,7 +814,6 @@ void ttlGenericCommand(client *c, int output_ms) {
/* The key exists. Return -1 if it has no expire, or the actual /* The key exists. Return -1 if it has no expire, or the actual
* TTL value otherwise. */ * TTL value otherwise. */
std::unique_lock<fastlock> ul(g_expireLock);
expireEntry *pexpire = c->db->getExpire(c->argv[1]); expireEntry *pexpire = c->db->getExpire(c->argv[1]);
if (c->argc == 2) { if (c->argc == 2) {
@@ -783,18 +903,11 @@ expireEntryFat::~expireEntryFat()
} }
expireEntryFat::expireEntryFat(const expireEntryFat &e) expireEntryFat::expireEntryFat(const expireEntryFat &e)
: m_keyPrimary(e.m_keyPrimary), m_vecexpireEntries(e.m_vecexpireEntries) : m_vecexpireEntries(e.m_vecexpireEntries)
{ {
// Note: dictExpires is not copied // Note: dictExpires is not copied
} }
expireEntryFat::expireEntryFat(expireEntryFat &&e)
: m_keyPrimary(std::move(e.m_keyPrimary)), m_vecexpireEntries(std::move(e.m_vecexpireEntries))
{
m_dictIndex = e.m_dictIndex;
e.m_dictIndex = nullptr;
}
void expireEntryFat::createIndex() void expireEntryFat::createIndex()
{ {
serverAssert(m_dictIndex == nullptr); serverAssert(m_dictIndex == nullptr);
+125 -109
View File
@@ -22,9 +22,11 @@ public:
{} {}
subexpireEntry(const subexpireEntry &other) subexpireEntry(const subexpireEntry &other)
: spsubkey((const char*)sdsdupshared(other.spsubkey.get()), sdsfree) : spsubkey(nullptr, sdsfree)
{ {
when = other.when; when = other.when;
if (other.spsubkey != nullptr)
spsubkey = std::unique_ptr<const char, void(*)(const char*)>((const char*)sdsdupshared(other.spsubkey.get()), sdsfree);
} }
subexpireEntry(subexpireEntry &&) = default; subexpireEntry(subexpireEntry &&) = default;
@@ -41,27 +43,30 @@ public:
}; };
private: private:
sdsimmutablestring m_keyPrimary;
std::vector<subexpireEntry> m_vecexpireEntries; // Note a NULL for the sds portion means the expire is for the primary key std::vector<subexpireEntry> m_vecexpireEntries; // Note a NULL for the sds portion means the expire is for the primary key
dict *m_dictIndex = nullptr; dict *m_dictIndex = nullptr;
long long m_whenPrimary = LLONG_MAX;
void createIndex(); void createIndex();
public: public:
expireEntryFat(const sdsimmutablestring &keyPrimary) expireEntryFat() = default;
: m_keyPrimary(keyPrimary) expireEntryFat(const expireEntryFat &);
{}
~expireEntryFat(); ~expireEntryFat();
expireEntryFat(const expireEntryFat &e);
expireEntryFat(expireEntryFat &&e);
long long when() const noexcept { return m_vecexpireEntries.front().when; } long long when() const noexcept { return m_vecexpireEntries.front().when; }
const char *key() const noexcept { return static_cast<const char*>(m_keyPrimary); }
bool operator<(long long when) const noexcept { return this->when() < when; } bool operator<(long long when) const noexcept { return this->when() < when; }
void expireSubKey(const char *szSubkey, long long when); void expireSubKey(const char *szSubkey, long long when);
bool FGetPrimaryExpire(long long *pwhen) const {
if (m_whenPrimary != LLONG_MAX) {
*pwhen = m_whenPrimary;
return true;
}
return false;
}
bool FEmpty() const noexcept { return m_vecexpireEntries.empty(); } bool FEmpty() const noexcept { return m_vecexpireEntries.empty(); }
const subexpireEntry &nextExpireEntry() const noexcept { return m_vecexpireEntries.front(); } const subexpireEntry &nextExpireEntry() const noexcept { return m_vecexpireEntries.front(); }
void popfrontExpireEntry(); void popfrontExpireEntry();
@@ -70,19 +75,11 @@ public:
}; };
class expireEntry { class expireEntry {
struct struct {
{ uint64_t m_whenAndPtrUnion : 63,
sdsimmutablestring m_key; fFat : 1;
expireEntryFat *m_pfatentry = nullptr; } s;
} u; static_assert(sizeof(expireEntryFat*) <= sizeof(int64_t), "The pointer must fit in the union");
long long m_when; // bit wise and with FFatMask means this is a fat entry and we should use the pointer
/* Mask to check if an entry is Fat, most significant bit of m_when being set means it is Fat otherwise it is not */
long long FFatMask() const noexcept {
return (1LL) << (sizeof(long long)*CHAR_BIT - 1);
}
expireEntry() = default;
public: public:
class iter class iter
{ {
@@ -118,92 +115,107 @@ public:
const iter &operator*() const { return *this; } const iter &operator*() const { return *this; }
}; };
expireEntry(sds key, const char *subkey, long long when) expireEntry()
{
s.fFat = 0;
s.m_whenAndPtrUnion = 0;
}
expireEntry(const char *subkey, long long when)
{ {
if (subkey != nullptr) if (subkey != nullptr)
{ {
m_when = FFatMask() | INVALID_EXPIRE; auto pfatentry = new (MALLOC_LOCAL) expireEntryFat();
u.m_pfatentry = new (MALLOC_LOCAL) expireEntryFat(sdsimmutablestring(sdsdupshared(key))); pfatentry->expireSubKey(subkey, when);
u.m_pfatentry->expireSubKey(subkey, when); s.m_whenAndPtrUnion = reinterpret_cast<long long>(pfatentry);
s.fFat = true;
} }
else else
{ {
u.m_key = sdsimmutablestring(sdsdupshared(key)); s.m_whenAndPtrUnion = when;
m_when = when; s.fFat = false;
} }
} }
expireEntry(const expireEntry &e)
{
*this = e;
}
expireEntry(expireEntry &&e)
{
u.m_key = std::move(e.u.m_key);
u.m_pfatentry = std::move(e.u.m_pfatentry);
m_when = e.m_when;
e.m_when = 0;
e.u.m_pfatentry = nullptr;
}
expireEntry(expireEntryFat *pfatentry) expireEntry(expireEntryFat *pfatentry)
{ {
u.m_pfatentry = pfatentry; assert(pfatentry != nullptr);
m_when = FFatMask() | INVALID_EXPIRE; s.m_whenAndPtrUnion = reinterpret_cast<long long>(pfatentry);
for (auto itr : *this) s.fFat = true;
{ }
if (itr.subkey() == nullptr)
{ expireEntry(const expireEntry &e) {
m_when = FFatMask() | itr.when(); if (e.FFat()) {
break; s.m_whenAndPtrUnion = reinterpret_cast<long long>(new expireEntryFat(*e.pfatentry()));
} s.fFat = true;
} else {
s = e.s;
} }
} }
expireEntry(expireEntry &&e)
{
s = e.s;
}
expireEntry &operator=(expireEntry &&e)
{
if (FFat())
delete pfatentry();
s = e.s;
e.s.m_whenAndPtrUnion = 0;
e.s.fFat = false;
return *this;
}
expireEntry &operator=(expireEntry &e) {
if (FFat())
delete pfatentry();
if (e.FFat()) {
s.m_whenAndPtrUnion = reinterpret_cast<long long>(new expireEntryFat(*e.pfatentry()));
s.fFat = true;
} else {
s = e.s;
}
return *this;
}
// Duplicate the expire, note this is intended to be passed directly to setExpire // Duplicate the expire, note this is intended to be passed directly to setExpire
expireEntry duplicate() const { expireEntry duplicate() const {
expireEntry dst; expireEntry dst;
dst.m_when = m_when;
if (FFat()) { if (FFat()) {
dst.u.m_pfatentry = new expireEntryFat(*u.m_pfatentry); auto pfatentry = new expireEntryFat(*expireEntry::pfatentry());
dst.s.m_whenAndPtrUnion = reinterpret_cast<long long>(pfatentry);
dst.s.fFat = true;
} else { } else {
dst.u.m_key = u.m_key; dst.s.m_whenAndPtrUnion = s.m_whenAndPtrUnion;
dst.s.fFat = false;
} }
return dst; return dst;
} }
void reset() {
if (FFat())
delete pfatentry();
s.fFat = false;
s.m_whenAndPtrUnion = 0;
}
~expireEntry() ~expireEntry()
{ {
if (FFat()) if (FFat())
delete u.m_pfatentry; delete pfatentry();
} }
expireEntry &operator=(const expireEntry &e) inline bool FFat() const noexcept { return s.fFat; }
{ expireEntryFat *pfatentry() {
u.m_key = e.u.m_key; assert(FFat());
m_when = e.m_when; return reinterpret_cast<expireEntryFat*>(s.m_whenAndPtrUnion);
if (e.FFat()) }
u.m_pfatentry = new (MALLOC_LOCAL) expireEntryFat(*e.u.m_pfatentry); const expireEntryFat *pfatentry() const {
return *this; return const_cast<expireEntry*>(this)->pfatentry();
} }
void setKeyUnsafe(sds key)
{
if (FFat())
u.m_pfatentry->m_keyPrimary = sdsimmutablestring(sdsdupshared(key));
else
u.m_key = sdsimmutablestring(sdsdupshared(key));
}
inline bool FFat() const noexcept { return m_when & FFatMask(); }
expireEntryFat *pfatentry() { assert(FFat()); return u.m_pfatentry; }
const expireEntryFat *pfatentry() const { assert(FFat()); return u.m_pfatentry; }
bool operator==(const sdsview &key) const noexcept
{
return key == this->key();
}
bool operator<(const expireEntry &e) const noexcept bool operator<(const expireEntry &e) const noexcept
{ {
@@ -214,17 +226,11 @@ public:
return this->when() < when; return this->when() < when;
} }
const char *key() const noexcept
{
if (FFat())
return u.m_pfatentry->key();
return static_cast<const char*>(u.m_key);
}
long long when() const noexcept long long when() const noexcept
{ {
if (FFat()) if (FFat())
return u.m_pfatentry->when(); return pfatentry()->when();
return FGetPrimaryExpire(); return s.m_whenAndPtrUnion;
} }
void update(const char *subkey, long long when) void update(const char *subkey, long long when)
@@ -233,30 +239,27 @@ public:
{ {
if (subkey == nullptr) if (subkey == nullptr)
{ {
m_when = when; s.m_whenAndPtrUnion = when;
return; return;
} }
else else
{ {
// we have to upgrade to a fat entry // we have to upgrade to a fat entry
long long whenT = m_when; auto pfatentry = new (MALLOC_LOCAL) expireEntryFat();
sdsimmutablestring keyPrimary = u.m_key; pfatentry->expireSubKey(nullptr, s.m_whenAndPtrUnion);
m_when |= FFatMask(); s.m_whenAndPtrUnion = reinterpret_cast<long long>(pfatentry);
u.m_pfatentry = new (MALLOC_LOCAL) expireEntryFat(keyPrimary); s.fFat = true;
u.m_pfatentry->expireSubKey(nullptr, whenT);
// at this point we're fat so fall through // at this point we're fat so fall through
} }
} }
if (subkey == nullptr) pfatentry()->expireSubKey(subkey, when);
m_when = when | FFatMask();
u.m_pfatentry->expireSubKey(subkey, when);
} }
iter begin() const { return iter(this, 0); } iter begin() const { return iter(this, 0); }
iter end() const iter end() const
{ {
if (FFat()) if (FFat())
return iter(this, u.m_pfatentry->size()); return iter(this, pfatentry()->size());
return iter(this, 1); return iter(this, 1);
} }
@@ -268,26 +271,39 @@ public:
pfatentry()->m_vecexpireEntries.begin() + itr.m_idx); pfatentry()->m_vecexpireEntries.begin() + itr.m_idx);
} }
size_t size() const size_t size() const {
{
if (FFat()) if (FFat())
return u.m_pfatentry->size(); return pfatentry()->size();
return 1; return 1;
} }
long long FGetPrimaryExpire() const noexcept
{
return m_when & (~FFatMask());
}
bool FGetPrimaryExpire(long long *pwhen) const noexcept bool FGetPrimaryExpire(long long *pwhen) const noexcept
{ {
*pwhen = FGetPrimaryExpire(); if (FFat()) {
return *pwhen != INVALID_EXPIRE; return pfatentry()->FGetPrimaryExpire(pwhen);
} else {
*pwhen = s.m_whenAndPtrUnion;
return true;
}
}
void *release_as_void() {
uint64_t whenT = s.m_whenAndPtrUnion;
whenT |= static_cast<uint64_t>(s.fFat) << 63;
s.m_whenAndPtrUnion = 0;
s.fFat = 0;
return reinterpret_cast<void*>(whenT);
}
static expireEntry *from_void(void **src) {
uintptr_t llV = reinterpret_cast<uintptr_t>(src);
return reinterpret_cast<expireEntry*>(llV);
}
static const expireEntry *from_void(void *const*src) {
uintptr_t llV = reinterpret_cast<uintptr_t>(src);
return reinterpret_cast<expireEntry*>(llV);
} }
explicit operator sdsview() const noexcept { return key(); }
explicit operator long long() const noexcept { return when(); } explicit operator long long() const noexcept { return when(); }
}; };
typedef semiorderedset<expireEntry, sdsview, true /*expireEntry can be memmoved*/> expireset; static_assert(sizeof(expireEntry) == sizeof(long long), "This must fit in a long long so it can be put in a dictEntry");
extern fastlock g_expireLock;
+2 -2
View File
@@ -46,7 +46,7 @@
#include <deque> #include <deque>
extern "C" { extern "C" {
#include <sds.h> /* Use hiredis sds. */ #include <sds.h> /* Use hiredis sds. */
#include <sdscompat.h> #include "sdscompat.h"
#include "hiredis.h" #include "hiredis.h"
} }
#include "ae.h" #include "ae.h"
@@ -904,7 +904,7 @@ int main(int argc, const char **argv) {
while (self_threads < config.max_threads) { while (self_threads < config.max_threads) {
for (int i = 0; i < config.numclients; i++) { for (int i = 0; i < config.numclients; i++) {
snprintf(command, 63, "SET %d %s\r\n", self_threads * config.numclients + i, set_value); snprintf(command, sizeof(command), "SET %d %s\r\n", self_threads * config.numclients + i, set_value);
createClient(command, strlen(command), NULL,self_threads); createClient(command, strlen(command), NULL,self_threads);
} }
+2 -6
View File
@@ -20,11 +20,9 @@ void lazyfreeFreeObject(void *args[]) {
* when the database was logically deleted. */ * when the database was logically deleted. */
void lazyfreeFreeDatabase(void *args[]) { void lazyfreeFreeDatabase(void *args[]) {
dict *ht1 = (dict *) args[0]; dict *ht1 = (dict *) args[0];
expireset *setexpire = (expireset *) args[1];
size_t numkeys = dictSize(ht1); size_t numkeys = dictSize(ht1);
dictRelease(ht1); dictRelease(ht1);
delete setexpire;
atomicDecr(lazyfree_objects,numkeys); atomicDecr(lazyfree_objects,numkeys);
atomicIncr(lazyfreed_objects,numkeys); atomicIncr(lazyfreed_objects,numkeys);
} }
@@ -217,17 +215,15 @@ void freeObjAsync(robj *key, robj *obj) {
* create a new empty set of hash tables and scheduling the old ones for * create a new empty set of hash tables and scheduling the old ones for
* lazy freeing. */ * lazy freeing. */
void redisDbPersistentData::emptyDbAsync() { void redisDbPersistentData::emptyDbAsync() {
std::unique_lock<fastlock> ul(g_expireLock);
dict *oldht1 = m_pdict; dict *oldht1 = m_pdict;
auto *set = m_setexpire;
m_setexpire = new (MALLOC_LOCAL) expireset();
m_pdict = dictCreate(&dbDictType,this); m_pdict = dictCreate(&dbDictType,this);
if (m_spstorage != nullptr) if (m_spstorage != nullptr)
m_spstorage->clearAsync(); m_spstorage->clearAsync();
if (m_fTrackingChanges) if (m_fTrackingChanges)
m_fAllChanged = true; m_fAllChanged = true;
atomicIncr(lazyfree_objects,dictSize(oldht1)); atomicIncr(lazyfree_objects,dictSize(oldht1));
bioCreateLazyFreeJob(lazyfreeFreeDatabase,2,oldht1,set); m_numexpires = 0;
bioCreateLazyFreeJob(lazyfreeFreeDatabase,2,oldht1,nullptr);
} }
/* Release the radix tree mapping Redis Cluster keys to slots asynchronously. */ /* Release the radix tree mapping Redis Cluster keys to slots asynchronously. */
+33
View File
@@ -0,0 +1,33 @@
#include <string>
#include <fstream>
#include <limits>
static size_t getMemKey(std::string key) {
# ifdef __linux__
std::string token;
std::ifstream f("/proc/meminfo");
while (f >> token) {
if (token == key) {
size_t mem_val;
if (f >> mem_val) {
return mem_val * 1024; // values are in kB
} else {
return 0;
}
f.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
return 0;
# else
(void)key;
return 0;
# endif
}
size_t getMemAvailable() {
return getMemKey("MemAvailable:");
}
size_t getMemTotal() {
return getMemKey("MemTotal:");
}
+9 -9
View File
@@ -656,7 +656,7 @@ void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
/* We don't need to do anything here if the server isn't inside /* We don't need to do anything here if the server isn't inside
* a transaction. */ * a transaction. */
if (!g_pserver->propagate_in_transaction) return; if (!serverTL->propagate_in_transaction) return;
/* If this command is executed from with Lua or MULTI/EXEC we do not /* If this command is executed from with Lua or MULTI/EXEC we do not
* need to propagate EXEC */ * need to propagate EXEC */
@@ -690,8 +690,9 @@ void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
} }
/* Free the context after the user function was called. */ /* Free the context after the user function was called. */
void moduleFreeContext(RedisModuleCtx *ctx) { void moduleFreeContext(RedisModuleCtx *ctx, bool propogate) {
moduleHandlePropagationAfterCommandCallback(ctx); if (propogate)
moduleHandlePropagationAfterCommandCallback(ctx);
autoMemoryCollect(ctx); autoMemoryCollect(ctx);
poolAllocRelease(ctx); poolAllocRelease(ctx);
if (ctx->postponed_arrays) { if (ctx->postponed_arrays) {
@@ -1814,7 +1815,7 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
* the module command was called by a script. */ * the module command was called by a script. */
if (serverTL->in_eval || serverTL->in_exec) return; if (serverTL->in_eval || serverTL->in_exec) return;
/* If we already emitted MULTI return ASAP. */ /* If we already emitted MULTI return ASAP. */
if (g_pserver->propagate_in_transaction) return; if (serverTL->propagate_in_transaction) return;
/* If this is a thread safe context, we do not want to wrap commands /* If this is a thread safe context, we do not want to wrap commands
* executed into MULTI/EXEC, they are executed as single commands * executed into MULTI/EXEC, they are executed as single commands
* from an external client in essence. */ * from an external client in essence. */
@@ -2442,11 +2443,10 @@ int RM_UnlinkKey(RedisModuleKey *key) {
* If no TTL is associated with the key or if the key is empty, * If no TTL is associated with the key or if the key is empty,
* REDISMODULE_NO_EXPIRE is returned. */ * REDISMODULE_NO_EXPIRE is returned. */
mstime_t RM_GetExpire(RedisModuleKey *key) { mstime_t RM_GetExpire(RedisModuleKey *key) {
std::unique_lock<fastlock> ul(g_expireLock); auto itr = key->db->find(key->key);
expireEntry *pexpire = key->db->getExpire(key->key);
mstime_t expire = INVALID_EXPIRE; mstime_t expire = INVALID_EXPIRE;
if (pexpire != nullptr) if (itr->FExpires())
pexpire->FGetPrimaryExpire(&expire); itr->expire.FGetPrimaryExpire(&expire);
if (expire == INVALID_EXPIRE || key->value == NULL) if (expire == INVALID_EXPIRE || key->value == NULL)
return REDISMODULE_NO_EXPIRE; return REDISMODULE_NO_EXPIRE;
@@ -7058,7 +7058,7 @@ RedisModuleString *RM_DictPrev(RedisModuleCtx *ctx, RedisModuleDictIter *di, voi
/* Compare the element currently pointed by the iterator to the specified /* Compare the element currently pointed by the iterator to the specified
* element given by key/keylen, according to the operator 'op' (the set of * element given by key/keylen, according to the operator 'op' (the set of
* valid operators are the same valid for RedisModule_DictIteratorStart). * valid operators are the same valid for RedisModule_DictIteratorStart).
* If the comparision is successful the command returns REDISMODULE_OK * If the comparison is successful the command returns REDISMODULE_OK
* otherwise REDISMODULE_ERR is returned. * otherwise REDISMODULE_ERR is returned.
* *
* This is useful when we want to just emit a lexicographical range, so * This is useful when we want to just emit a lexicographical range, so
+5 -1
View File
@@ -13,7 +13,10 @@ endif
.SUFFIXES: .c .so .xo .o .SUFFIXES: .c .so .xo .o
all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so build-keydb_modstatsd
build-keydb_modstatsd:
$(MAKE) -C keydb_modstatsd
.c.xo: .c.xo:
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@ $(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
@@ -60,3 +63,4 @@ helloacl.so: helloacl.xo
clean: clean:
rm -rf *.xo *.so rm -rf *.xo *.so
$(MAKE) -C keydb_modstatsd clean
+3 -2
View File
@@ -51,8 +51,9 @@ int TimerCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
for (int j = 0; j < 10; j++) { for (int j = 0; j < 10; j++) {
int delay = rand() % 5000; int delay = rand() % 5000;
char *buf = RedisModule_Alloc(256); int bufsize = 256;
snprintf(buf,256,"After %d", delay); char *buf = RedisModule_Alloc(bufsize);
snprintf(buf,bufsize,"After %d", delay);
RedisModuleTimerID tid = RedisModule_CreateTimer(ctx,delay,timerHandler,buf); RedisModuleTimerID tid = RedisModule_CreateTimer(ctx,delay,timerHandler,buf);
REDISMODULE_NOT_USED(tid); REDISMODULE_NOT_USED(tid);
} }
+13
View File
@@ -0,0 +1,13 @@
MODULE_FLAGS := -fPIC -O2 -Wall -Werror
OBJECT_FILES := modmain.o
MODSNAP_CXX_FLAGS := -std=gnu++14
%.o: %.cpp
$(CXX) -o $@ -c $< $(MODULE_FLAGS) -I../../../deps/cpp-statsd-client/include $(MODSNAP_CXX_FLAGS) -g
modstatsd.so: $(OBJECT_FILES)
$(CXX) -shared $(OBJECT_FILES) -o modstatsd.so
clean:
rm -f $(OBJECT_FILES) modstatsd.so
+722
View File
@@ -0,0 +1,722 @@
#include "redismodule.h"
#include <stdlib.h>
#include <cpp-statsd-client/StatsdClient.hpp>
#include <unordered_map>
#include <sys/utsname.h>
#include <algorithm>
#ifdef __linux__
#include <sys/sysinfo.h>
#include <sys/resource.h>
#endif
#include <inttypes.h>
#include <sys/time.h>
#include <regex>
#include <sstream>
#include <iostream>
#include <fstream>
using namespace Statsd;
class StatsdClientWrapper
{
private:
StatsdClient *m_stats;
StatsdClient *m_stats_noprefix;
public:
StatsdClientWrapper(const std::string& host,
const uint16_t port,
const std::string& prefix,
const uint64_t batchsize,
const uint64_t sendInterval) {
m_stats = new StatsdClient(host, port, prefix, batchsize, sendInterval);
m_stats_noprefix = new StatsdClient(host, port, "keydb", batchsize, sendInterval);
}
~StatsdClientWrapper() {
delete m_stats;
delete m_stats_noprefix;
}
void increment(const std::string& key, const bool prefixOnly = true) {
m_stats->increment(key);
if (!prefixOnly) m_stats_noprefix->increment(key);
}
void decrement(const std::string& key, const bool prefixOnly = true) {
m_stats->decrement(key);
if (!prefixOnly) m_stats_noprefix->decrement(key);
}
void count(const std::string& key, const int delta, const bool prefixOnly = true) {
m_stats->count(key, delta);
if (!prefixOnly) m_stats_noprefix->count(key, delta);
}
template <typename T>
void gauge(const std::string& key, const T value, const bool prefixOnly = true) {
m_stats->gauge(key, value);
if (!prefixOnly) m_stats_noprefix->gauge(key, value);
}
void timing(const std::string& key, const unsigned int ms, const bool prefixOnly = true) {
m_stats->timing(key, ms);
if (!prefixOnly) m_stats_noprefix->timing(key, ms);
}
};
/* constants */
static time_t c_infoUpdateSeconds = 10;
// the current Redis Cluster setup we configure replication factor as 2, each non-empty master node should have 2 replicas, given that there are 3 zones in each regions
static const int EXPECTED_NUMBER_OF_REPLICAS = 2;
StatsdClientWrapper *g_stats = nullptr;
std::string m_strPrefix { "keydb" };
const std::regex g_replica_or_db_info_regex { "^(slave|db)(\\d+)" };
const char *g_string_counter_separator = "__";
const uint64_t g_stats_buffer_size_bytes = 1600;
std::string nodeName;
int unameResult;
enum class StatsD_Type {
STATSD_GAUGE_LONGLONG,
STATSD_GAUGE_FLOAT,
STATSD_GAUGE_BYTES,
STATSD_DELTA,
STATSD_COUNTER_STRING
};
struct StatsRecord {
StatsD_Type type;
bool prefixOnly = true;
const char *szAlternate = nullptr;
/* Dynamic Values */
long long prevVal = 0;
};
std::unordered_map<std::string, StatsRecord> g_mapInfoFields = {
// info
{ "used_memory", { StatsD_Type::STATSD_GAUGE_BYTES, false /* prefixOnly */}},
{ "used_memory_rss", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "maxmemory", { StatsD_Type::STATSD_GAUGE_BYTES, false /* prefixOnly */}},
{ "used_memory_dataset_perc", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "avg_lock_contention", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "repl_backlog_size", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "connected_slaves", { StatsD_Type::STATSD_GAUGE_LONGLONG, true, "connected_replicas" }},
{ "errorstat_ERR", { StatsD_Type::STATSD_DELTA }},
{ "connected_clients", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "cluster_connections", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "instantaneous_ops_per_sec", { StatsD_Type::STATSD_GAUGE_LONGLONG, false /* prefixOnly */}},
{ "instantaneous_input_kbps", { StatsD_Type::STATSD_GAUGE_FLOAT, false /* prefixOnly */}},
{ "instantaneous_output_kbps", { StatsD_Type::STATSD_GAUGE_FLOAT, false /* prefixOnly */}},
{ "server_threads", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "mvcc_depth", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "sync_full", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "sync_partial_ok", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "sync_partial_err", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "rdb_bgsave_in_progress", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "rdb_last_bgsave_time_sec", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "used_memory_overhead", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "master_sync_in_progress", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "uptime_in_seconds", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "hz", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "configured_hz", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "maxclients", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "client_recent_max_input_buffer", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "client_recent_max_output_buffer", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "blocked_clients", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "tracking_clients", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "clients_in_timeout_table", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "used_memory_peak", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "used_memory_startup", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "used_memory_dataset", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "allocator_allocated", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "allocator_active", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "allocator_resident", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "total_system_memory", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "used_memory_lua", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "used_memory_scripts", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "number_of_cached_scripts", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "allocator_frag_ratio", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "allocator_frag_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "allocator_rss_ratio", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "allocator_rss_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "rss_overhead_ratio", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "rss_overhead_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "mem_fragmentation_ratio", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "mem_fragmentation_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "mem_not_counted_for_evict", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "mem_replication_backlog", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "mem_clients_slaves", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "mem_clients_normal", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "mem_aof_buffer", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "active_defrag_running", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "lazyfree_pending_objects", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "lazyfreed_objects", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "loading", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "current_cow_peak", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "current_cow_size", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "current_cow_size_age", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "current_fork_perc", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "current_save_keys_processed", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "current_save_keys_total", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "rdb_changes_since_last_save", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "rdb_last_save_time", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "rdb_last_cow_size", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "aof_enabled", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "aof_rewrite_in_progress", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "aof_rewrite_scheduled", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "aof_last_cow_size", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "module_fork_in_progress", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "module_fork_last_cow_size", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "aof_current_size", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "aof_base_size", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "aof_pending_rewrite", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "aof_buffer_length", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "aof_rewrite_buffer_length", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "aof_pending_bio_fsync", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "aof_delayed_fsync", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "total_connections_received", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "total_commands_processed", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "total_net_input_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "total_net_output_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "rejected_connections", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "expired_keys", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "expired_stale_perc", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "expired_time_cap_reached_count", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "expire_cycle_cpu_milliseconds", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "evicted_keys", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "keyspace_hits", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "keyspace_misses", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "pubsub_channels", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "pubsub_patterns", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "latest_fork_usec", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "total_forks", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "migrate_cached_sockets", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "slave_expires_tracked_keys", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "active_defrag_hits", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "active_defrag_misses", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "active_defrag_key_hits", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "active_defrag_key_misses", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "tracking_total_keys", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "tracking_total_items", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "tracking_total_prefixes", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "unexpected_error_replies", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "total_error_replies", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "dump_payload_sanitizations", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "total_reads_processed", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "total_writes_processed", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "instantaneous_lock_contention", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "avg_lock_contention", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "storage_provider_read_hits", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "storage_provider_read_misses", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "repl_backlog_active", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "repl_backlog_size", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "repl_backlog_first_byte_offset", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "repl_backlog_histlen", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "used_cpu_sys", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "used_cpu_user", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "used_cpu_sys_children", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "used_cpu_user_children", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "used_cpu_user_children", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "long_lock_waits", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "used_cpu_sys_main_thread", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "used_cpu_user_main_thread", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "master_sync_total_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "master_sync_read_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "master_sync_last_io_seconds_ago", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "master_link_down_since_seconds", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "maxmemory_policy", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "role", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "master_global_link_status", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "master_link_status", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "master_failover_state", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "rdb_last_bgsave_status", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "rdb_saves", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "instantaneous_input_repl_kbps", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "instantaneous_output_repl_kbps", { StatsD_Type::STATSD_GAUGE_FLOAT }},
{ "master_host", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "master_repl_offset", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "second_repl_offset", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "slave_repl_offset", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "redis_version", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "redis_git_sha1", { StatsD_Type::STATSD_COUNTER_STRING }},
// cluster info
{ "cluster_state", { StatsD_Type::STATSD_COUNTER_STRING }},
{ "cluster_slots_assigned", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "cluster_slots_ok", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "cluster_slots_pfail", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "cluster_slots_fail", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "cluster_known_nodes", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "cluster_size", { StatsD_Type::STATSD_GAUGE_LONGLONG }},
{ "storage_flash_available_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
{ "storage_flash_total_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }},
};
/* Globals */
static uint64_t g_cclients = 0;
long long ustime(void) {
struct timeval tv;
long long ust;
gettimeofday(&tv, NULL);
ust = ((long long)tv.tv_sec)*1000000;
ust += tv.tv_usec;
return ust;
}
void event_client_change_handler(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data) {
if (eid.id != REDISMODULE_EVENT_CLIENT_CHANGE)
return;
uint64_t clientsStart = g_cclients;
switch (subevent) {
case REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED:
++g_cclients;
g_stats->increment("clients_added");
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric increment for \"clients_added\"");
break;
case REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED:
--g_cclients;
g_stats->increment("clients_disconnected");
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric increment for \"clients_disconnected\"");
break;
}
if (g_cclients != clientsStart) {
g_stats->gauge("clients", g_cclients);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"clients\": %" PRIu64, g_cclients);
}
}
void handleStatItem(struct RedisModuleCtx *ctx, std::string name, StatsRecord &record, const char *pchValue) {
switch (record.type) {
case StatsD_Type::STATSD_GAUGE_LONGLONG: {
long long val = strtoll(pchValue, nullptr, 10);
g_stats->gauge(name, val, record.prefixOnly);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %lld", name.c_str(), val);
break;
}
case StatsD_Type::STATSD_GAUGE_FLOAT: {
double val = strtod(pchValue, nullptr);
g_stats->gauge(name, val, record.prefixOnly);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %f", name.c_str(), val);
break;
}
case StatsD_Type::STATSD_GAUGE_BYTES: {
long long val = strtoll(pchValue, nullptr, 10);
g_stats->gauge(name + "_MB", val / 1024/1024, record.prefixOnly);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %llu", (name + "_MB").c_str(), val / 1024/1024);
break;
}
case StatsD_Type::STATSD_DELTA: {
long long val = strtoll(pchValue, nullptr, 10);
g_stats->count(name, val - record.prevVal, record.prefixOnly);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric count for \"%s\": %lld", name.c_str() , val - record.prevVal);
record.prevVal = val;
break;
}
case StatsD_Type::STATSD_COUNTER_STRING: {
// parse val string
const char *pNewLine = strchr(pchValue, '\r');
if (pNewLine == nullptr) {
pNewLine = strchr(pchValue, '\n');
}
if (pNewLine == nullptr) {
g_stats->increment("STATSD_COUNTER_STRING_failed", 1);
return;
}
std::string val(pchValue, pNewLine - pchValue);
std::replace(val.begin(), val.end(), '.', '-');
// metrics emit
std::string metricsName = name + g_string_counter_separator + val;
g_stats->increment(metricsName, 1);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\"", metricsName.c_str());
break;
}
default:
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unknown stats record type for the key \"%s\": %u", name.c_str(), (unsigned)record.type);
break;
}
}
void handleErrorStatItem(struct RedisModuleCtx *ctx, std::string name, std::string rest) {
size_t idx = rest.find('=');
if (idx != std::string::npos) {
std::string statValue = rest.substr(idx + 1);
long long val = strtoll(statValue.c_str(), nullptr, 10);
g_stats->gauge(name, val);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %lld", name.c_str(), val);
} else {
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unexpected errorstat line format returned by \"INFO\" command: \"%s\"", (name + rest).c_str());
}
}
void handleReplicaOrDbInfoItem(struct RedisModuleCtx *ctx, std::string name, std::string rest) {
//use a stringstream to extract each metric of the form <name>=<value>
std::stringstream metrics(rest);
while (metrics.good()) {
std::string metric;
std::getline(metrics, metric, ',');
size_t idx = metric.find('=');
if (idx != std::string::npos) {
std::string stat = metric.substr(0, idx);
std::string statName = name + '-' + stat;
//idx + 1 to ignore the = sign
std::string statValue = metric.substr(idx + 1);
// string values
if (stat == "ip" || stat == "state") {
std::replace(statValue.begin(), statValue.end(), '.', '-');
statName += g_string_counter_separator + statValue;
g_stats->increment(statName, 1);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\"", statName.c_str());
} else {
long long val = strtoll(statValue.c_str(), nullptr, 10);
g_stats->gauge(statName, val);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %lld", statName.c_str(), val);
}
}
}
}
void handle_info_response(struct RedisModuleCtx *ctx, const char *szReply, size_t len, const char *command) {
#define SAFETY_CHECK_POINTER(_p) ((_p) < (szReply + len))
// Parse the INFO reply string line by line
const char *pchLineStart = szReply;
while (SAFETY_CHECK_POINTER(pchLineStart) && *pchLineStart != '\0') {
// Loop Each Line
const char *pchColon = pchLineStart;
while (SAFETY_CHECK_POINTER(pchColon) && *pchColon != ':' && *pchColon != '\r') {
++pchColon;
}
if (!SAFETY_CHECK_POINTER(pchColon)) {
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unexpected line termination when parsing response from %s command: \"%s\"", command, pchLineStart);
break; // BUG
}
const char *pchLineEnd = pchColon;
while (SAFETY_CHECK_POINTER(pchLineEnd) && *pchLineEnd != '\n')
++pchLineEnd;
std::string strCheck(pchLineStart, pchColon - pchLineStart);
if (strCheck.find("errorstat_") != std::string::npos) {
std::string remainder(pchColon + 1, pchLineEnd - (pchColon + 1));
handleErrorStatItem(ctx, strCheck, remainder);
} else if (std::regex_match(strCheck, g_replica_or_db_info_regex)) {
std::string remainder(pchColon + 1, pchLineEnd - (pchColon + 1));
handleReplicaOrDbInfoItem(ctx, strCheck, remainder);
} else {
auto itr = g_mapInfoFields.find(strCheck);
if (itr != g_mapInfoFields.end()) {
// This is an info field we care about
if (itr->second.szAlternate != nullptr)
strCheck = itr->second.szAlternate;
handleStatItem(ctx, strCheck, itr->second, pchColon+1);
}
}
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "INFO response line: \"%s\"", std::string(pchLineStart, pchLineEnd - pchLineStart).c_str());
pchLineStart = pchLineEnd + 1; // start of next line, if we're over the loop will catch it
}
#undef SAFETY_CHECK_POINTER
}
void handle_cluster_nodes_response(struct RedisModuleCtx *ctx, const char *szReply, size_t len) {
#define SAFETY_CHECK_POINTER(_p) ((_p) < (szReply + len))
const char *pchLineStart = szReply;
long long primaries = 0;
long long replicas = 0;
while (SAFETY_CHECK_POINTER(pchLineStart) && *pchLineStart != '\0') {
// Loop Each Line
const char *pchLineEnd = pchLineStart;
while (SAFETY_CHECK_POINTER(pchLineEnd) && (*pchLineEnd != '\r') && (*pchLineEnd != '\n')) {
++pchLineEnd;
}
std::string line(pchLineStart, pchLineEnd - pchLineStart);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Cluster Nodes Line: \"%s\"", line.c_str());
if (std::string::npos != line.find("master")) {
++primaries;
} else if (std::string::npos != line.find("slave")) {
++replicas;
} else {
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unexpected NODE format returned by \"CLUSTER NODES\" command: \"%s\"", line.c_str());
}
// emit myself stat
if (line.find("myself") != std::string::npos) {
size_t firstSpaceIdx = line.find(' ');
// emit cluster node id
if (firstSpaceIdx != std::string::npos) {
std::string nodeIdStat = "cluster_node_id";
nodeIdStat += g_string_counter_separator + line.substr(0, firstSpaceIdx);
g_stats->increment(nodeIdStat);
}
// emit node ip
size_t firstColonIdx = line.find(':');
if (firstColonIdx != std::string::npos) {
std::string nodeIpStat = "cluster_node_ip";
std::string nodeIP = line.substr(firstSpaceIdx+1, firstColonIdx-firstSpaceIdx-1);
std::replace(nodeIP.begin(), nodeIP.end(), '.', '-');
nodeIpStat += g_string_counter_separator + nodeIP;
g_stats->increment(nodeIpStat);
}
}
pchLineStart = pchLineEnd;
while (SAFETY_CHECK_POINTER(pchLineStart) && ((*pchLineStart == '\r') || (*pchLineStart == '\n'))) {
++pchLineStart;
}
}
g_stats->gauge("primaries", primaries);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"primaries\": %llu", primaries);
g_stats->gauge("replicas", replicas);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"replicas\": %llu", replicas);
#undef SAFETY_CHECK_POINTER
}
void handle_client_list_response(struct RedisModuleCtx *ctx, const char *szReply, size_t len) {
size_t totalClientOutputBuffer = 0;
size_t totalReplicaClientOutputBuffer = 0;
#define SAFETY_CHECK_POINTER(_p) ((_p) < (szReply + len))
const char *pchLineStart = szReply;
while (SAFETY_CHECK_POINTER(pchLineStart) && *pchLineStart != '\0') {
// Loop Each Line
const char *pchLineEnd = pchLineStart;
while (SAFETY_CHECK_POINTER(pchLineEnd) && (*pchLineEnd != '\r') && (*pchLineEnd != '\n')) {
++pchLineEnd;
}
std::string line(pchLineStart, pchLineEnd - pchLineStart);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Client List Line: \"%s\"", line.c_str());
// recover output buffer size for client
bool lineFailed = false;
bool replica = line.find("flags=S") != std::string::npos;
size_t idx = line.find("omem");
if (!(lineFailed = (idx == std::string::npos))) {
std::string rest = line.substr(idx);
size_t startIdx = rest.find("=");
if (!(lineFailed = (startIdx == std::string::npos))) {
size_t endIdx = rest.find(" ");
if (!(lineFailed = (endIdx == std::string::npos))) {
// use startIdx + 1 and endIdx - 1 to exclude the '=' and ' ' characters
std::string valueString = rest.substr(startIdx + 1, (endIdx - 1) - (startIdx + 1));
size_t value = strtoll(valueString.c_str(), nullptr, 10);
totalClientOutputBuffer += value;
if (replica) {
totalReplicaClientOutputBuffer += value;
}
}
}
}
if (lineFailed) {
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unexpected CLIENT format returned by \"CLIENT LIST\" command: \"%s\"", line.c_str());
}
pchLineStart = pchLineEnd;
while (SAFETY_CHECK_POINTER(pchLineStart) && ((*pchLineStart == '\r') || (*pchLineStart == '\n'))) {
++pchLineStart;
}
}
#undef SAFETY_CHECK_POINTER
g_stats->gauge("total_client_output_buffer", totalClientOutputBuffer);
g_stats->gauge("total_replica_client_output_buffer", totalReplicaClientOutputBuffer);
}
void emit_system_free_memory() {
std::ifstream meminfo("/proc/meminfo");
std::string line;
while (std::getline(meminfo, line)) {
if (line.find("MemAvailable:") != std::string::npos) {
unsigned long memAvailableInKB;
std::sscanf(line.c_str(), "MemAvailable: %lu kB", &memAvailableInKB);
g_stats->gauge("systemAvailableMemory_MB", memAvailableInKB / 1024);
return;
}
}
}
void emit_metrics_for_insufficient_replicas(struct RedisModuleCtx *ctx, long long keys) {
// non-empty
if (keys <= 0) {
return;
}
RedisModuleCallReply *reply = RedisModule_Call(ctx, "ROLE", "");
if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ARRAY) {
RedisModule_FreeCallReply(reply);
return;
}
RedisModuleCallReply *roleReply = RedisModule_CallReplyArrayElement(reply, 0);
if (RedisModule_CallReplyType(roleReply) != REDISMODULE_REPLY_STRING) {
RedisModule_FreeCallReply(reply);
return;
}
size_t len;
const char *role = RedisModule_CallReplyStringPtr(roleReply, &len);
// check if the current node is a primary
if (strncmp(role, "master", len) == 0) {
RedisModuleCallReply *replicasReply = RedisModule_CallReplyArrayElement(reply, 2);
// check if there are less than 2 connected replicas
if (RedisModule_CallReplyLength(replicasReply) < EXPECTED_NUMBER_OF_REPLICAS) {
g_stats->increment("lessThanExpectedReplicas_error", 1);
}
}
RedisModule_FreeCallReply(reply);
}
void event_cron_handler(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data) {
static time_t lastTime = 0;
time_t curTime = time(nullptr);
if ((curTime - lastTime) > c_infoUpdateSeconds) {
size_t startTime = ustime();
#ifdef __linux__
/* Log CPU Usage */
static long long s_mscpuLast = 0;
struct rusage self_ru;
getrusage(RUSAGE_SELF, &self_ru);
long long mscpuCur = (self_ru.ru_utime.tv_sec * 1000) + (self_ru.ru_utime.tv_usec / 1000)
+ (self_ru.ru_stime.tv_sec * 1000) + (self_ru.ru_stime.tv_usec / 1000);
g_stats->gauge("cpu_load_perc", ((double)(mscpuCur - s_mscpuLast) / ((curTime - lastTime)*1000))*100, false /* prefixOnly */);
s_mscpuLast = mscpuCur;
#endif
/* Log clients */
g_stats->gauge("clients", g_cclients);
/* node name */
if (unameResult == 0) {
g_stats->increment("node_name" + std::string(g_string_counter_separator) + nodeName);
}
/* Log INFO Fields */
size_t commandStartTime = ustime();
RedisModuleCallReply *reply = RedisModule_Call(ctx, "INFO", "");
size_t len = 0;
const char *szReply = RedisModule_CallReplyStringPtr(reply, &len);
g_stats->timing("info_time_taken_us", ustime() - commandStartTime);
commandStartTime = ustime();
handle_info_response(ctx, szReply, len, "INFO");
g_stats->timing("handle_info_time_taken_us", ustime() - commandStartTime);
RedisModule_FreeCallReply(reply);
/* Log CLUSTER INFO Fields */
commandStartTime = ustime();
reply = RedisModule_Call(ctx, "CLUSTER", "c", "INFO");
szReply = RedisModule_CallReplyStringPtr(reply, &len);
g_stats->timing("cluster_info_time_taken_us", ustime() - commandStartTime);
commandStartTime = ustime();
handle_info_response(ctx, szReply, len, "CLUSTER INFO");
g_stats->timing("handle_cluster_info_time_taken_us", ustime() - commandStartTime);
RedisModule_FreeCallReply(reply);
/* Log Cluster Topology */
commandStartTime = ustime();
reply = RedisModule_Call(ctx, "CLUSTER", "c", "NODES");
szReply = RedisModule_CallReplyStringPtr(reply, &len);
g_stats->timing("cluster_nodes_time_taken_us", ustime() - commandStartTime);
commandStartTime = ustime();
handle_cluster_nodes_response(ctx, szReply, len);
g_stats->timing("handle_cluster_nodes_time_taken_us", ustime() - commandStartTime);
RedisModule_FreeCallReply(reply);
/* Log Client Info */
// commandStartTime = ustime();
// reply = RedisModule_Call(ctx, "CLIENT", "c", "LIST");
// szReply = RedisModule_CallReplyStringPtr(reply, &len);
// g_stats->timing("client_info_time_taken_us", ustime() - commandStartTime);
// commandStartTime = ustime();
// handle_client_list_response(ctx, szReply, len);
// g_stats->timing("handle_client_info_time_taken_us", ustime() - commandStartTime);
// RedisModule_FreeCallReply(reply);
commandStartTime = ustime();
emit_system_free_memory();
g_stats->timing("emit_free_system_memory_time_taken_us", ustime() - commandStartTime);
/* Log Keys */
commandStartTime = ustime();
reply = RedisModule_Call(ctx, "dbsize", "");
long long keys = RedisModule_CallReplyInteger(reply);
RedisModule_FreeCallReply(reply);
g_stats->gauge("keys", keys, false /* prefixOnly */);
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"keys\": %llu", keys);
g_stats->timing("emit_keys_metric_time_taken_us", ustime() - commandStartTime);
emit_metrics_for_insufficient_replicas(ctx, keys);
g_stats->timing("metrics_time_taken_us", ustime() - startTime);
lastTime = curTime;
}
}
extern "C" int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (RedisModule_Init(ctx,"statsd",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModule_AutoMemory(ctx);
/* Use pod name if available*/
const char *podName = getenv("POD_NAME");
utsname sysName;
unameResult = uname(&sysName);
if (unameResult == 0) {
nodeName = std::string(sysName.nodename);
std::replace(nodeName.begin(), nodeName.end(), '.', '-');
}
if (podName != nullptr) {
m_strPrefix = podName;
std::replace(m_strPrefix.begin(), m_strPrefix.end(), '.', '-');
}
else if (unameResult == 0) {
m_strPrefix = nodeName;
unameResult = 1;
}
for (int iarg = 0; iarg < argc; ++iarg) {
size_t len = 0;
const char *rgchArg = RedisModule_StringPtrLen(argv[iarg], &len);
if (len == 6 && memcmp(rgchArg, "prefix", 6) == 0) {
if ((iarg+1) >= argc) {
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Expected a value after 'prefix'");
return REDISMODULE_ERR;
}
++iarg;
size_t lenPrefix = 0;
const char *rgchPrefix = RedisModule_StringPtrLen(argv[iarg], &lenPrefix);
m_strPrefix = std::string(rgchPrefix, lenPrefix);
} else {
RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unrecognized configuration flag");
return REDISMODULE_ERR;
}
}
g_stats = new StatsdClientWrapper("localhost", 8125, m_strPrefix, g_stats_buffer_size_bytes, c_infoUpdateSeconds * 1000);
if (RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_ClientChange, event_client_change_handler) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_CronLoop, event_cron_handler) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
extern "C" int RedisModule_OnUnload(RedisModuleCtx *ctx) {
delete g_stats;
return REDISMODULE_OK;
}
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -1,6 +1,6 @@
#ifdef CLIENT #ifdef CLIENT
extern "C" { extern "C" {
#include <sdscompat.h> #include "sdscompat.h"
#include <sds.h> #include <sds.h>
} }
#else #else
@@ -19,7 +19,7 @@ extern "C" {
#ifdef MOTD #ifdef MOTD
#include <curl/curl.h> #include <curl/curl.h>
#ifdef CLIENT #if !defined(USE_SYSTEM_HIREDIS) && defined(CLIENT)
extern "C" { extern "C" {
__attribute__ ((weak)) hisds hi_sdscatlen(hisds s, const void *t, size_t len) { __attribute__ ((weak)) hisds hi_sdscatlen(hisds s, const void *t, size_t len) {
return sdscatlen(s, t, len); return sdscatlen(s, t, len);
+23 -5
View File
@@ -118,14 +118,14 @@ void discardCommand(client *c) {
void beforePropagateMulti() { void beforePropagateMulti() {
/* Propagating MULTI */ /* Propagating MULTI */
serverAssert(!g_pserver->propagate_in_transaction); serverAssert(!serverTL->propagate_in_transaction);
g_pserver->propagate_in_transaction = 1; serverTL->propagate_in_transaction = 1;
} }
void afterPropagateExec() { void afterPropagateExec() {
/* Propagating EXEC */ /* Propagating EXEC */
serverAssert(g_pserver->propagate_in_transaction == 1); serverAssert(serverTL->propagate_in_transaction == 1);
g_pserver->propagate_in_transaction = 0; serverTL->propagate_in_transaction = 0;
} }
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand /* Send a MULTI command to all the slaves and AOF file. Check the execCommand
@@ -264,7 +264,7 @@ void execCommand(client *c) {
/* Make sure the EXEC command will be propagated as well if MULTI /* Make sure the EXEC command will be propagated as well if MULTI
* was already propagated. */ * was already propagated. */
if (g_pserver->propagate_in_transaction) { if (serverTL->propagate_in_transaction) {
int is_master = listLength(g_pserver->masters) == 0; int is_master = listLength(g_pserver->masters) == 0;
g_pserver->dirty++; g_pserver->dirty++;
/* If inside the MULTI/EXEC block this instance was suddenly /* If inside the MULTI/EXEC block this instance was suddenly
@@ -429,6 +429,24 @@ void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) {
dictReleaseIterator(di); dictReleaseIterator(di);
} }
/* Update the DB of the watchedKey structure incase if the c->db (currently selected DB) is updated.
* For example, it may happen during the SWAPDB.
* watchForKey() sets the original DB of the watchedKey structure with the c->db
* but the c->db can be updated incase of SWAPDB command. */
void updateDBWatchedKey(int dbid, client *c) {
listIter li;
listNode *ln;
watchedKey *wk;
listRewind(c->watched_keys,&li);
while((ln = listNext(&li))) {
wk = (watchedKey*)listNodeValue(ln);
if (wk->db->id == dbid) {
wk->db = c->db;
}
}
}
void watchCommand(client *c) { void watchCommand(client *c) {
int j; int j;
+36 -28
View File
@@ -145,7 +145,7 @@ client *createClient(connection *conn, int iel) {
client_id = g_pserver->next_client_id.fetch_add(1); client_id = g_pserver->next_client_id.fetch_add(1);
c->iel = iel; c->iel = iel;
c->id = client_id; c->id = client_id;
snprintf(c->lock.szName, 56, "client %" PRIu64, client_id); snprintf(c->lock.szName, sizeof(c->lock.szName), "client %" PRIu64, client_id);
c->resp = 2; c->resp = 2;
c->conn = conn; c->conn = conn;
c->name = NULL; c->name = NULL;
@@ -163,7 +163,6 @@ client *createClient(connection *conn, int iel) {
c->multibulklen = 0; c->multibulklen = 0;
c->bulklen = -1; c->bulklen = -1;
c->sentlen = 0; c->sentlen = 0;
c->sentlenAsync = 0;
c->flags = 0; c->flags = 0;
c->fPendingAsyncWrite = FALSE; c->fPendingAsyncWrite = FALSE;
c->fPendingAsyncWriteHandler = FALSE; c->fPendingAsyncWriteHandler = FALSE;
@@ -777,11 +776,11 @@ void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
* we return NULL in addReplyDeferredLen() */ * we return NULL in addReplyDeferredLen() */
if (node == NULL) return; if (node == NULL) return;
char lenstr[128]; char lenstr[128];
size_t lenstr_len = snprintf(lenstr, 128, "%c%ld\r\n", prefix, length); size_t lenstr_len = snprintf(lenstr, sizeof(lenstr), "%c%ld\r\n", prefix, length);
setDeferredReply(c, node, lenstr, lenstr_len); setDeferredReply(c, node, lenstr, lenstr_len);
} else { } else {
char lenstr[128]; char lenstr[128];
int lenstr_len = snprintf(lenstr, 128, "%c%ld\r\n", prefix, length); int lenstr_len = snprintf(lenstr, sizeof(lenstr), "%c%ld\r\n", prefix, length);
size_t idxSplice = (size_t)node; size_t idxSplice = (size_t)node;
serverAssert(idxSplice <= c->replyAsync->used); serverAssert(idxSplice <= c->replyAsync->used);
@@ -1177,6 +1176,11 @@ int chooseBestThreadForAccept()
void clientAcceptHandler(connection *conn) { void clientAcceptHandler(connection *conn) {
client *c = (client*)connGetPrivateData(conn); client *c = (client*)connGetPrivateData(conn);
if (conn->flags & CONN_FLAG_AUDIT_LOGGING_REQUIRED) {
c->flags |= CLIENT_AUDIT_LOGGING;
c->fprint = conn->fprint;
}
if (connGetState(conn) != CONN_STATE_CONNECTED) { if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Error accepting a client connection: %s", "Error accepting a client connection: %s",
@@ -1240,6 +1244,7 @@ void clientAcceptHandler(connection *conn) {
#define MAX_ACCEPTS_PER_CALL 1000 #define MAX_ACCEPTS_PER_CALL 1000
#define MAX_ACCEPTS_PER_CALL_TLS 100 #define MAX_ACCEPTS_PER_CALL_TLS 100
static void acceptCommonHandler(connection *conn, int flags, char *ip, int iel) { static void acceptCommonHandler(connection *conn, int flags, char *ip, int iel) {
client *c; client *c;
char conninfo[100]; char conninfo[100];
@@ -1276,24 +1281,27 @@ static void acceptCommonHandler(connection *conn, int flags, char *ip, int iel)
* called, because we don't want to even start transport-level negotiation * called, because we don't want to even start transport-level negotiation
* if rejected. */ * if rejected. */
if (listLength(g_pserver->clients) + getClusterConnectionsCount() if (listLength(g_pserver->clients) + getClusterConnectionsCount()
>= g_pserver->maxclients) >= (g_pserver->maxclients - g_pserver->maxclientsReserved))
{ {
const char *err; // Allow the connection if it comes from localhost and we're within the maxclientReserved buffer range
if (g_pserver->cluster_enabled) if ((listLength(g_pserver->clients) + getClusterConnectionsCount()) >= g_pserver->maxclients || strcmp("127.0.0.1", ip)) {
err = "-ERR max number of clients + cluster " const char *err;
"connections reached\r\n"; if (g_pserver->cluster_enabled)
else err = "-ERR max number of clients + cluster "
err = "-ERR max number of clients reached\r\n"; "connections reached\r\n";
else
err = "-ERR max number of clients reached\r\n";
/* That's a best effort error message, don't check write errors. /* That's a best effort error message, don't check write errors.
* Note that for TLS connections, no handshake was done yet so nothing * Note that for TLS connections, no handshake was done yet so nothing
* is written and the connection will just drop. */ * is written and the connection will just drop. */
if (connWrite(conn,err,strlen(err)) == -1) { if (connWrite(conn,err,strlen(err)) == -1) {
/* Nothing to do, Just to avoid the warning... */ /* Nothing to do, Just to avoid the warning... */
}
g_pserver->stat_rejected_conn++;
connClose(conn);
return;
} }
g_pserver->stat_rejected_conn++;
connClose(conn);
return;
} }
/* Create connection and client */ /* Create connection and client */
@@ -2595,9 +2603,7 @@ void parseClientCommandBuffer(client *c) {
(g_pserver->m_pstorageFactory || aeLockContested(cserver.cthreads/2) || cserver.cthreads == 1) && !GlobalLocksAcquired()) { (g_pserver->m_pstorageFactory || aeLockContested(cserver.cthreads/2) || cserver.cthreads == 1) && !GlobalLocksAcquired()) {
auto &query = c->vecqueuedcmd.back(); auto &query = c->vecqueuedcmd.back();
if (query.argc > 0 && query.argc == query.argcMax) { if (query.argc > 0 && query.argc == query.argcMax) {
if (c->db->prefetchKeysAsync(c, query, c->vecqueuedcmd.size() == 1)) { c->db->prefetchKeysAsync(c, query);
c->vecqueuedcmd.erase(c->vecqueuedcmd.begin());
}
} }
} }
c->reqtype = 0; c->reqtype = 0;
@@ -2755,7 +2761,7 @@ void readQueryFromClient(connection *conn) {
if (cserver.cthreads > 1 || g_pserver->m_pstorageFactory) { if (cserver.cthreads > 1 || g_pserver->m_pstorageFactory) {
parseClientCommandBuffer(c); parseClientCommandBuffer(c);
if (g_pserver->enable_async_commands && !serverTL->disable_async_commands && listLength(g_pserver->monitors) == 0 && (aeLockContention() || serverTL->rgdbSnapshot[c->db->id] || g_fTestMode)) { if (g_pserver->enable_async_commands && !serverTL->disable_async_commands && listLength(g_pserver->monitors) == 0 && (aeLockContention() || serverTL->rgdbSnapshot[c->db->id] || g_fTestMode) && !serverTL->in_eval && !serverTL->in_exec) {
// Frequent writers aren't good candidates for this optimization, they cause us to renew the snapshot too often // Frequent writers aren't good candidates for this optimization, they cause us to renew the snapshot too often
// so we exclude them unless the snapshot we need already exists. // so we exclude them unless the snapshot we need already exists.
// Note: In test mode we want to create snapshots as often as possibl to excercise them - we don't care about perf // Note: In test mode we want to create snapshots as often as possibl to excercise them - we don't care about perf
@@ -3250,16 +3256,18 @@ NULL
} }
else else
{ {
int iel = client->iel;
freeClientAsync(client); freeClientAsync(client);
aePostFunction(g_pserver->rgthreadvar[client->iel].el, [iel] { // note: failure is OK
freeClientsInAsyncFreeQueue(iel);
});
} }
} }
killed++; killed++;
} }
for (int iel = 0; iel < cserver.cthreads; ++iel) {
aePostFunction(g_pserver->rgthreadvar[iel].el, [iel] { // note: failure is OK
freeClientsInAsyncFreeQueue(iel);
});
}
/* Reply according to old/new format. */ /* Reply according to old/new format. */
if (c->argc == 3) { if (c->argc == 3) {
if (killed == 0) if (killed == 0)
@@ -3997,7 +4005,7 @@ void pauseClients(mstime_t end, pause_type type) {
* to track this state so that we don't assert * to track this state so that we don't assert
* in propagate(). */ * in propagate(). */
if (serverTL->in_exec) { if (serverTL->in_exec) {
g_pserver->client_pause_in_transaction = 1; serverTL->client_pause_in_transaction = 1;
} }
} }
-7
View File
@@ -41,10 +41,3 @@ void operator delete(void *p, std::size_t) noexcept
} }
#endif #endif
#if defined(USE_JEMALLOC)
extern "C" size_t malloc_usable_size(void *ptr)
{
return zmalloc_usable_size(ptr);
}
#endif
+5 -13
View File
@@ -46,6 +46,7 @@ robj *createObject(int type, void *ptr) {
char *oB = (char*)zcalloc(sizeof(robj)+mvccExtraBytes, MALLOC_SHARED); char *oB = (char*)zcalloc(sizeof(robj)+mvccExtraBytes, MALLOC_SHARED);
robj *o = reinterpret_cast<robj*>(oB + mvccExtraBytes); robj *o = reinterpret_cast<robj*>(oB + mvccExtraBytes);
new (o) redisObject;
o->type = type; o->type = type;
o->encoding = OBJ_ENCODING_RAW; o->encoding = OBJ_ENCODING_RAW;
o->m_ptr = ptr; o->m_ptr = ptr;
@@ -418,6 +419,7 @@ void decrRefCount(robj_roptr o) {
case OBJ_NESTEDHASH: freeNestedHashObject(o); break; case OBJ_NESTEDHASH: freeNestedHashObject(o); break;
default: serverPanic("Unknown object type"); break; default: serverPanic("Unknown object type"); break;
} }
o->~redisObject();
if (g_pserver->fActiveReplica) { if (g_pserver->fActiveReplica) {
zfree(reinterpret_cast<redisObjectExtended*>(o.unsafe_robjcast())-1); zfree(reinterpret_cast<redisObjectExtended*>(o.unsafe_robjcast())-1);
} else { } else {
@@ -1141,10 +1143,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mh->db[mh->num_dbs].overhead_ht_main = mem; mh->db[mh->num_dbs].overhead_ht_main = mem;
mem_total+=mem; mem_total+=mem;
std::unique_lock<fastlock> ul(g_expireLock); mh->db[mh->num_dbs].overhead_ht_expires = 0;
mem = db->setexpire()->estimated_bytes_used();
mh->db[mh->num_dbs].overhead_ht_expires = mem;
mem_total+=mem;
mh->num_dbs++; mh->num_dbs++;
} }
@@ -1541,7 +1540,7 @@ void memoryCommand(client *c) {
} else if (!strcasecmp(szFromObj(c->argv[1]),"malloc-stats") && c->argc == 2) { } else if (!strcasecmp(szFromObj(c->argv[1]),"malloc-stats") && c->argc == 2) {
#if defined(USE_JEMALLOC) #if defined(USE_JEMALLOC)
sds info = sdsempty(); sds info = sdsempty();
je_malloc_stats_print(inputCatSds, &info, NULL); malloc_stats_print(inputCatSds, &info, NULL);
addReplyVerbatim(c,info,sdslen(info),"txt"); addReplyVerbatim(c,info,sdslen(info),"txt");
sdsfree(info); sdsfree(info);
#else #else
@@ -1628,7 +1627,7 @@ robj *deserializeStoredStringObject(const char *data, size_t cb)
return newObject; return newObject;
} }
robj *deserializeStoredObjectCore(const void *data, size_t cb) robj *deserializeStoredObject(const void *data, size_t cb)
{ {
switch (((char*)data)[0]) switch (((char*)data)[0])
{ {
@@ -1665,13 +1664,6 @@ robj *deserializeStoredObjectCore(const void *data, size_t cb)
serverPanic("Unknown object type loading from storage"); serverPanic("Unknown object type loading from storage");
} }
robj *deserializeStoredObject(const redisDbPersistentData *db, const char *key, const void *data, size_t cb)
{
robj *o = deserializeStoredObjectCore(data, cb);
o->SetFExpires(db->setexpire()->exists(key));
return o;
}
sds serializeStoredObject(robj_roptr o, sds sdsPrefix) sds serializeStoredObject(robj_roptr o, sds sdsPrefix)
{ {
switch (o->type) switch (o->type)
+139 -67
View File
@@ -1163,7 +1163,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj_roptr key, robj_roptr val, const expireEn
char szT[32]; char szT[32];
if (g_pserver->fActiveReplica) { if (g_pserver->fActiveReplica) {
snprintf(szT, 32, "%" PRIu64, mvccFromObj(val)); snprintf(szT, sizeof(szT), "%" PRIu64, mvccFromObj(val));
if (rdbSaveAuxFieldStrStr(rdb,"mvcc-tstamp", szT) == -1) return -1; if (rdbSaveAuxFieldStrStr(rdb,"mvcc-tstamp", szT) == -1) return -1;
} }
@@ -1190,7 +1190,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj_roptr key, robj_roptr val, const expireEn
{ {
if (itr.subkey() == nullptr) if (itr.subkey() == nullptr)
continue; // already saved continue; // already saved
snprintf(szT, 32, "%lld", itr.when()); snprintf(szT, sizeof(szT), "%lld", itr.when());
rdbSaveAuxFieldStrStr(rdb,"keydb-subexpire-key",itr.subkey()); rdbSaveAuxFieldStrStr(rdb,"keydb-subexpire-key",itr.subkey());
rdbSaveAuxFieldStrStr(rdb,"keydb-subexpire-when",szT); rdbSaveAuxFieldStrStr(rdb,"keydb-subexpire-when",szT);
} }
@@ -1237,18 +1237,14 @@ int rdbSaveInfoAuxFields(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
return 1; return 1;
} }
int saveKey(rio *rdb, const redisDbPersistentDataSnapshot *db, int flags, size_t *processed, const char *keystr, robj_roptr o) int saveKey(rio *rdb, int flags, size_t *processed, const char *keystr, robj_roptr o)
{ {
redisObjectStack key; redisObjectStack key;
initStaticStringObject(key,(char*)keystr); initStaticStringObject(key,(char*)keystr);
std::unique_lock<fastlock> ul(g_expireLock, std::defer_lock);
const expireEntry *pexpire = nullptr; const expireEntry *pexpire = nullptr;
if (o->FExpires()) if (o->FExpires()) {
{ pexpire = &o->expire;
ul.lock();
pexpire = db->getExpire(&key);
serverAssert((o->FExpires() && pexpire != nullptr) || (!o->FExpires() && pexpire == nullptr));
} }
if (rdbSaveKeyValuePair(rdb,&key,o,pexpire) == -1) if (rdbSaveKeyValuePair(rdb,&key,o,pexpire) == -1)
@@ -1334,7 +1330,7 @@ int rdbSaveRio(rio *rdb, const redisDbPersistentDataSnapshot **rgpdb, int *error
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr; if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
for (j = 0; j < cserver.dbnum; j++) { for (j = 0; j < cserver.dbnum; j++) {
const redisDbPersistentDataSnapshot *db = rgpdb[j]; const redisDbPersistentDataSnapshot *db = rgpdb != nullptr ? rgpdb[j] : g_pserver->db[j];
if (db->size() == 0) continue; if (db->size() == 0) continue;
/* Write the SELECT DB opcode */ /* Write the SELECT DB opcode */
@@ -1355,7 +1351,7 @@ int rdbSaveRio(rio *rdb, const redisDbPersistentDataSnapshot **rgpdb, int *error
if (o->FExpires()) if (o->FExpires())
++ckeysExpired; ++ckeysExpired;
if (!saveKey(rdb, db, rdbflags, &processed, keystr, o)) if (!saveKey(rdb, rdbflags, &processed, keystr, o))
return false; return false;
/* Update child info every 1 second (approximately). /* Update child info every 1 second (approximately).
@@ -1490,7 +1486,7 @@ int rdbSaveFile(char *filename, const redisDbPersistentDataSnapshot **rgpdb, rdb
rio rdb; rio rdb;
int error = 0; int error = 0;
snprintf(tmpfile,256,"temp-%d-%d.rdb", getpid(), g_pserver->rdbThreadVars.tmpfileNum); getTempFileName(tmpfile, g_pserver->rdbThreadVars.tmpfileNum);
fp = fopen(tmpfile,"w"); fp = fopen(tmpfile,"w");
if (!fp) { if (!fp) {
char *cwdp = getcwd(cwd,MAXPATHLEN); char *cwdp = getcwd(cwd,MAXPATHLEN);
@@ -1660,12 +1656,14 @@ int launchRdbSaveThread(pthread_t &child, rdbSaveInfo *rsi)
pthread_attr_t tattr; pthread_attr_t tattr;
pthread_attr_init(&tattr); pthread_attr_init(&tattr);
pthread_attr_setstacksize(&tattr, 1 << 23); // 8 MB pthread_attr_setstacksize(&tattr, 1 << 23); // 8 MB
openChildInfoPipe();
if (pthread_create(&child, &tattr, rdbSaveThread, args)) { if (pthread_create(&child, &tattr, rdbSaveThread, args)) {
pthread_attr_destroy(&tattr); pthread_attr_destroy(&tattr);
for (int idb = 0; idb < cserver.dbnum; ++idb) for (int idb = 0; idb < cserver.dbnum; ++idb)
g_pserver->db[idb]->endSnapshot(args->rgpdb[idb]); g_pserver->db[idb]->endSnapshot(args->rgpdb[idb]);
args->~rdbSaveThreadArgs(); args->~rdbSaveThreadArgs();
zfree(args); zfree(args);
closeChildInfoPipe();
return C_ERR; return C_ERR;
} }
pthread_attr_destroy(&tattr); pthread_attr_destroy(&tattr);
@@ -1679,24 +1677,23 @@ int rdbSaveBackground(rdbSaveInfo *rsi) {
pthread_t child; pthread_t child;
long long start; long long start;
if (hasActiveChildProcess()) return C_ERR; if (hasActiveChildProcessOrBGSave()) return C_ERR;
g_pserver->dirty_before_bgsave = g_pserver->dirty; g_pserver->dirty_before_bgsave = g_pserver->dirty;
g_pserver->lastbgsave_try = time(NULL); g_pserver->lastbgsave_try = time(NULL);
openChildInfoPipe();
start = ustime(); start = ustime();
latencyStartMonitor(g_pserver->rdb_save_latency);
g_pserver->stat_fork_time = ustime()-start;
g_pserver->stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / g_pserver->stat_fork_time / (1024*1024*1024); /* GB per second. */
if (launchRdbSaveThread(child, rsi) != C_OK) { if (launchRdbSaveThread(child, rsi) != C_OK) {
closeChildInfoPipe();
g_pserver->lastbgsave_status = C_ERR; g_pserver->lastbgsave_status = C_ERR;
serverLog(LL_WARNING,"Can't save in background: fork: %s", serverLog(LL_WARNING,"Can't save in background: fork: %s",
strerror(errno)); strerror(errno));
return C_ERR; return C_ERR;
} }
g_pserver->stat_fork_time = ustime()-start;
g_pserver->stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / g_pserver->stat_fork_time / (1024*1024*1024); /* GB per second. */
latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000); latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000);
serverLog(LL_NOTICE,"Background saving started"); serverLog(LL_NOTICE,"Background saving started");
g_pserver->rdb_save_time_start = time(NULL); g_pserver->rdb_save_time_start = time(NULL);
@@ -1714,7 +1711,7 @@ void getTempFileName(char tmpfile[], int tmpfileNum) {
char tmpfileNumString[214]; char tmpfileNumString[214];
/* Generate temp rdb file name using aync-signal safe functions. */ /* Generate temp rdb file name using aync-signal safe functions. */
int pid_len = ll2string(pid, sizeof(pid), getpid()); int pid_len = ll2string(pid, sizeof(pid), g_pserver->in_fork_child ? getppid() : getpid());
int tmpfileNum_len = ll2string(tmpfileNumString, sizeof(tmpfileNumString), tmpfileNum); int tmpfileNum_len = ll2string(tmpfileNumString, sizeof(tmpfileNumString), tmpfileNum);
strcpy(tmpfile, "temp-"); strcpy(tmpfile, "temp-");
strncpy(tmpfile+5, pid, pid_len); strncpy(tmpfile+5, pid, pid_len);
@@ -2545,7 +2542,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int *error, uint64_t mvcc_ts
* encoding version in the lower 10 bits of the module ID. */ * encoding version in the lower 10 bits of the module ID. */
void *ptr = mt->rdb_load(&io,moduleid&1023); void *ptr = mt->rdb_load(&io,moduleid&1023);
if (io.ctx) { if (io.ctx) {
moduleFreeContext(io.ctx); moduleFreeContext(io.ctx, false /* propogate */);
zfree(io.ctx); zfree(io.ctx);
} }
@@ -3601,6 +3598,8 @@ static void backgroundSaveDoneHandlerDisk(int exitcode, bool fCancelled) {
g_pserver->dirty = g_pserver->dirty - g_pserver->dirty_before_bgsave; g_pserver->dirty = g_pserver->dirty - g_pserver->dirty_before_bgsave;
g_pserver->lastsave = time(NULL); g_pserver->lastsave = time(NULL);
g_pserver->lastbgsave_status = C_OK; g_pserver->lastbgsave_status = C_OK;
latencyEndMonitor(g_pserver->rdb_save_latency);
latencyAddSampleIfNeeded("rdb-save",g_pserver->rdb_save_latency);
} else if (!fCancelled && exitcode != 0) { } else if (!fCancelled && exitcode != 0) {
serverLog(LL_WARNING, "Background saving error"); serverLog(LL_WARNING, "Background saving error");
g_pserver->lastbgsave_status = C_ERR; g_pserver->lastbgsave_status = C_ERR;
@@ -3691,7 +3690,7 @@ void killRDBChild(bool fSynchronous) {
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
if (cserver.fForkBgSave) { if (cserver.fForkBgSave) {
kill(g_pserver->rdb_child_pid,SIGUSR1); kill(g_pserver->child_pid,SIGUSR1);
} else { } else {
g_pserver->rdbThreadVars.fRdbThreadCancel = true; g_pserver->rdbThreadVars.fRdbThreadCancel = true;
if (g_pserver->rdb_child_type == RDB_CHILD_TYPE_SOCKET) { if (g_pserver->rdb_child_type == RDB_CHILD_TYPE_SOCKET) {
@@ -3782,7 +3781,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
int pipefds[2]; int pipefds[2];
rdbSaveSocketThreadArgs *args = nullptr; rdbSaveSocketThreadArgs *args = nullptr;
if (hasActiveChildProcess()) return C_ERR; if (hasActiveChildProcessOrBGSave()) return C_ERR;
/* Even if the previous fork child exited, don't start a new one until we /* Even if the previous fork child exited, don't start a new one until we
* drained the pipe. */ * drained the pipe. */
@@ -3829,57 +3828,130 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
} }
/* Create the child process. */ /* Create the child process. */
openChildInfoPipe(); if (cserver.fForkBgSave) {
pid_t childpid;
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
/* Child */
int retval, dummy;
rio rdb;
for (int idb = 0; idb < cserver.dbnum; ++idb) rioInitWithFd(&rdb,args->rdb_pipe_write);
args->rgpdb[idb] = g_pserver->db[idb]->createSnapshot(getMvccTstamp(), false /*fOptional*/);
g_pserver->rdbThreadVars.tmpfileNum++; redisSetProcTitle("keydb-rdb-to-slaves");
g_pserver->rdbThreadVars.fRdbThreadCancel = false; redisSetCpuAffinity(g_pserver->bgsave_cpulist);
pthread_attr_t tattr;
pthread_attr_init(&tattr);
pthread_attr_setstacksize(&tattr, 1 << 23); // 8 MB
if (pthread_create(&child, &tattr, rdbSaveToSlavesSocketsThread, args)) {
pthread_attr_destroy(&tattr);
serverLog(LL_WARNING,"Can't save in background: fork: %s",
strerror(errno));
/* Undo the state change. The caller will perform cleanup on retval = rdbSaveRioWithEOFMark(&rdb,nullptr,nullptr,rsi);
* all the slaves in BGSAVE_START state, but an early call to if (retval == C_OK && rioFlush(&rdb) == 0)
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */ retval = C_ERR;
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) { if (retval == C_OK) {
client *replica = (client*)ln->value; sendChildCowInfo(CHILD_INFO_TYPE_RDB_COW_SIZE, "RDB");
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
replica->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
} }
}
close(args->rdb_pipe_write);
close(g_pserver->rdb_pipe_read);
zfree(g_pserver->rdb_pipe_conns);
close(args->safe_to_exit_pipe);
g_pserver->rdb_pipe_conns = NULL;
g_pserver->rdb_pipe_numconns = 0;
g_pserver->rdb_pipe_numconns_writing = 0;
args->rsi.~rdbSaveInfo();
zfree(args);
closeChildInfoPipe();
return C_ERR;
}
pthread_attr_destroy(&tattr);
g_pserver->child_type = CHILD_TYPE_RDB;
serverLog(LL_NOTICE,"Background RDB transfer started"); rioFreeFd(&rdb);
g_pserver->rdb_save_time_start = time(NULL); /* wake up the reader, tell it we're done. */
serverAssert(!g_pserver->rdbThreadVars.fRdbThreadActive); close(args->rdb_pipe_write);
g_pserver->rdbThreadVars.rdb_child_thread = child; close(g_pserver->rdb_child_exit_pipe); /* close write end so that we can detect the close on the parent. */
g_pserver->rdbThreadVars.fRdbThreadActive = true; /* hold exit until the parent tells us it's safe. we're not expecting
g_pserver->rdb_child_type = RDB_CHILD_TYPE_SOCKET; * to read anything, just get the error when the pipe is closed. */
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, []{ dummy = read(args->safe_to_exit_pipe, pipefds, 1);
if (aeCreateFileEvent(serverTL->el, g_pserver->rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) { UNUSED(dummy);
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event."); exitFromChild((retval == C_OK) ? 0 : 1);
} else {
/* Parent */
close(args->safe_to_exit_pipe);
if (childpid == -1) {
serverLog(LL_WARNING,"Can't save in background: fork: %s",
strerror(errno));
/* Undo the state change. The caller will perform cleanup on
* all the slaves in BGSAVE_START state, but an early call to
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *replica = (client*)ln->value;
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
replica->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
}
}
close(args->rdb_pipe_write);
close(g_pserver->rdb_pipe_read);
zfree(g_pserver->rdb_pipe_conns);
g_pserver->rdb_pipe_conns = NULL;
g_pserver->rdb_pipe_numconns = 0;
g_pserver->rdb_pipe_numconns_writing = 0;
args->rsi.~rdbSaveInfo();
zfree(args);
} else {
serverLog(LL_NOTICE,"Background RDB transfer started by pid %ld",
(long)childpid);
g_pserver->rdb_save_time_start = time(NULL);
g_pserver->rdb_child_type = RDB_CHILD_TYPE_SOCKET;
g_pserver->rdbThreadVars.fRdbThreadActive = true;
updateDictResizePolicy();
close(args->rdb_pipe_write); /* close write in parent so that it can detect the close on the child. */
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, []{
if (aeCreateFileEvent(serverTL->el, g_pserver->rdb_pipe_read, AE_READABLE, rdbPipeReadHandler, nullptr) == AE_ERR) {
serverPanic("Unrecoverable error creating g_pserver->rdb_pipe_read file event.");
}
});
}
return (childpid == -1) ? C_ERR : C_OK;
} }
}); }
else {
openChildInfoPipe();
for (int idb = 0; idb < cserver.dbnum; ++idb)
args->rgpdb[idb] = g_pserver->db[idb]->createSnapshot(getMvccTstamp(), false /*fOptional*/);
g_pserver->rdbThreadVars.tmpfileNum++;
g_pserver->rdbThreadVars.fRdbThreadCancel = false;
pthread_attr_t tattr;
pthread_attr_init(&tattr);
pthread_attr_setstacksize(&tattr, 1 << 23); // 8 MB
if (pthread_create(&child, &tattr, rdbSaveToSlavesSocketsThread, args)) {
pthread_attr_destroy(&tattr);
serverLog(LL_WARNING,"Can't save in background: fork: %s",
strerror(errno));
/* Undo the state change. The caller will perform cleanup on
* all the slaves in BGSAVE_START state, but an early call to
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *replica = (client*)ln->value;
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
replica->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
}
}
close(args->rdb_pipe_write);
close(g_pserver->rdb_pipe_read);
zfree(g_pserver->rdb_pipe_conns);
close(args->safe_to_exit_pipe);
g_pserver->rdb_pipe_conns = NULL;
g_pserver->rdb_pipe_numconns = 0;
g_pserver->rdb_pipe_numconns_writing = 0;
args->rsi.~rdbSaveInfo();
zfree(args);
closeChildInfoPipe();
return C_ERR;
}
pthread_attr_destroy(&tattr);
g_pserver->child_type = CHILD_TYPE_RDB;
serverLog(LL_NOTICE,"Background RDB transfer started");
g_pserver->rdb_save_time_start = time(NULL);
serverAssert(!g_pserver->rdbThreadVars.fRdbThreadActive);
g_pserver->rdbThreadVars.rdb_child_thread = child;
g_pserver->rdbThreadVars.fRdbThreadActive = true;
g_pserver->rdb_child_type = RDB_CHILD_TYPE_SOCKET;
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, []{
if (aeCreateFileEvent(serverTL->el, g_pserver->rdb_pipe_read, AE_READABLE, rdbPipeReadHandler, nullptr) == AE_ERR) {
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
}
});
}
return C_OK; /* Unreached. */ return C_OK; /* Unreached. */
} }
+1
View File
@@ -151,6 +151,7 @@ int rdbLoad(rdbSaveInfo *rsi, int rdbflags);
int rdbLoadFile(const char *filename, rdbSaveInfo *rsi, int rdbflags); int rdbLoadFile(const char *filename, rdbSaveInfo *rsi, int rdbflags);
int rdbSaveBackground(rdbSaveInfo *rsi); int rdbSaveBackground(rdbSaveInfo *rsi);
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi); int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
void getTempFileName(char tmpfile[], int tmpfileNum);
void rdbRemoveTempFile(pid_t childpid, int from_signal); void rdbRemoveTempFile(pid_t childpid, int from_signal);
int rdbSave(const redisDbPersistentDataSnapshot **rgpdb, rdbSaveInfo *rsi); int rdbSave(const redisDbPersistentDataSnapshot **rgpdb, rdbSaveInfo *rsi);
int rdbSaveFile(char *filename, const redisDbPersistentDataSnapshot **rgpdb, rdbSaveInfo *rsi); int rdbSaveFile(char *filename, const redisDbPersistentDataSnapshot **rgpdb, rdbSaveInfo *rsi);
+49 -28
View File
@@ -8,37 +8,46 @@ class readWriteLock {
int m_readCount = 0; int m_readCount = 0;
int m_writeCount = 0; int m_writeCount = 0;
bool m_writeWaiting = false; bool m_writeWaiting = false;
bool m_multi = true;
public: public:
readWriteLock(const char *name) : m_readLock(name), m_writeLock(name) {} readWriteLock(const char *name) : m_readLock(name), m_writeLock(name) {}
void acquireRead() { void acquireRead() {
std::unique_lock<fastlock> rm(m_readLock); std::unique_lock<fastlock> rm(m_readLock, std::defer_lock);
while (m_writeCount > 0 || m_writeWaiting) if (m_multi) {
m_cv.wait(rm); rm.lock();
while (m_writeCount > 0 || m_writeWaiting)
m_cv.wait(rm);
}
m_readCount++; m_readCount++;
} }
bool tryAcquireRead() { bool tryAcquireRead() {
std::unique_lock<fastlock> rm(m_readLock, std::defer_lock); std::unique_lock<fastlock> rm(m_readLock, std::defer_lock);
if (!rm.try_lock()) if (m_multi) {
return false; if (!rm.try_lock())
if (m_writeCount > 0 || m_writeWaiting) return false;
return false; if (m_writeCount > 0 || m_writeWaiting)
return false;
}
m_readCount++; m_readCount++;
return true; return true;
} }
void acquireWrite(bool exclusive = true) { void acquireWrite(bool exclusive = true) {
std::unique_lock<fastlock> rm(m_readLock); std::unique_lock<fastlock> rm(m_readLock, std::defer_lock);
m_writeWaiting = true; if (m_multi) {
while (m_readCount > 0) rm.lock();
m_cv.wait(rm); m_writeWaiting = true;
if (exclusive) { while (m_readCount > 0)
/* Another thread might have the write lock while we have the read lock
but won't be able to release it until they can acquire the read lock
so release the read lock and try again instead of waiting to avoid deadlock */
while(!m_writeLock.try_lock())
m_cv.wait(rm); m_cv.wait(rm);
if (exclusive) {
/* Another thread might have the write lock while we have the internal lock
but won't be able to release it until they can acquire the internal lock
so release the internal lock and try again instead of waiting to avoid deadlock */
while(!m_writeLock.try_lock())
m_cv.wait(rm);
}
} }
m_writeCount++; m_writeCount++;
m_writeWaiting = false; m_writeWaiting = false;
@@ -51,30 +60,38 @@ public:
bool tryAcquireWrite(bool exclusive = true) { bool tryAcquireWrite(bool exclusive = true) {
std::unique_lock<fastlock> rm(m_readLock, std::defer_lock); std::unique_lock<fastlock> rm(m_readLock, std::defer_lock);
if (!rm.try_lock()) if (m_multi) {
return false; if (!rm.try_lock())
if (m_readCount > 0)
return false;
if (exclusive)
if (!m_writeLock.try_lock())
return false; return false;
if (m_readCount > 0)
return false;
if (exclusive)
if (!m_writeLock.try_lock())
return false;
}
m_writeCount++; m_writeCount++;
return true; return true;
} }
void releaseRead() { void releaseRead() {
std::unique_lock<fastlock> rm(m_readLock); std::unique_lock<fastlock> rm(m_readLock, std::defer_lock);
if (m_multi) {
rm.lock();
m_cv.notify_all();
}
m_readCount--; m_readCount--;
m_cv.notify_all();
} }
void releaseWrite(bool exclusive = true) { void releaseWrite(bool exclusive = true) {
std::unique_lock<fastlock> rm(m_readLock); std::unique_lock<fastlock> rm(m_readLock, std::defer_lock);
serverAssert(m_writeCount > 0); serverAssert(m_writeCount > 0);
if (exclusive) if (m_multi) {
m_writeLock.unlock(); rm.lock();
if (exclusive)
m_writeLock.unlock();
m_cv.notify_all();
}
m_writeCount--; m_writeCount--;
m_cv.notify_all();
} }
void downgradeWrite(bool exclusive = true) { void downgradeWrite(bool exclusive = true) {
@@ -82,6 +99,10 @@ public:
acquireRead(); acquireRead();
} }
void setMulti(bool multi) {
m_multi = multi;
}
bool hasReader() { bool hasReader() {
return m_readCount > 0; return m_readCount > 0;
} }
+1 -1
View File
@@ -44,7 +44,7 @@
#include <pthread.h> #include <pthread.h>
#include <string> #include <string>
extern "C" { extern "C" {
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */ #include "sdscompat.h" /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
#include <sds.h> /* Use hiredis sds. */ #include <sds.h> /* Use hiredis sds. */
#include <hiredis.h> #include <hiredis.h>
} }
+2 -2
View File
@@ -445,7 +445,7 @@ extern "C" void clusterManagerWaitForClusterJoin(void) {
int counter = 0, int counter = 0,
check_after = CLUSTER_JOIN_CHECK_AFTER + check_after = CLUSTER_JOIN_CHECK_AFTER +
(int)(listLength(cluster_manager.nodes) * 0.15f); (int)(listLength(cluster_manager.nodes) * 0.15f);
while(!clusterManagerIsConfigConsistent()) { while(!clusterManagerIsConfigConsistent(0 /*fLog*/)) {
printf("."); printf(".");
fflush(stdout); fflush(stdout);
sleep(1); sleep(1);
@@ -588,7 +588,7 @@ extern "C" int clusterManagerCheckCluster(int quiet) {
int do_fix = config.cluster_manager_command.flags & int do_fix = config.cluster_manager_command.flags &
CLUSTER_MANAGER_CMD_FLAG_FIX; CLUSTER_MANAGER_CMD_FLAG_FIX;
if (!quiet) clusterManagerShowNodes(); if (!quiet) clusterManagerShowNodes();
consistent = clusterManagerIsConfigConsistent(); consistent = clusterManagerIsConfigConsistent(1 /*fLog*/);
if (!consistent) { if (!consistent) {
sds err = sdsnew("[ERR] Nodes don't agree about configuration!"); sds err = sdsnew("[ERR] Nodes don't agree about configuration!");
clusterManagerOnError(err); clusterManagerOnError(err);
+37 -22
View File
@@ -54,7 +54,7 @@
#include <openssl/err.h> #include <openssl/err.h>
#include <hiredis_ssl.h> #include <hiredis_ssl.h>
#endif #endif
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */ #include "sdscompat.h" /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ #include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
#include "adlist.h" #include "adlist.h"
#include "zmalloc.h" #include "zmalloc.h"
@@ -1618,6 +1618,8 @@ static int parseOptions(int argc, char **argv) {
fprintf(stderr, "Unknown --show-pushes value '%s' " fprintf(stderr, "Unknown --show-pushes value '%s' "
"(valid: '[y]es', '[n]o')\n", argval); "(valid: '[y]es', '[n]o')\n", argval);
} }
} else if (!strcmp(argv[i],"--force")) {
config.force_mode = 1;
} else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') { } else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
if (config.cluster_manager_command.argc == 0) { if (config.cluster_manager_command.argc == 0) {
int j = i + 1; int j = i + 1;
@@ -1793,6 +1795,7 @@ static void usage(void) {
" --verbose Verbose mode.\n" " --verbose Verbose mode.\n"
" --no-auth-warning Don't show warning message when using password on command\n" " --no-auth-warning Don't show warning message when using password on command\n"
" line interface.\n" " line interface.\n"
" --force Ignore validation and safety checks\n"
" --help Output this help and exit.\n" " --help Output this help and exit.\n"
" --version Output version and exit.\n" " --version Output version and exit.\n"
"\n"); "\n");
@@ -2521,7 +2524,10 @@ cleanup:
static int clusterManagerNodeConnect(clusterManagerNode *node) { static int clusterManagerNodeConnect(clusterManagerNode *node) {
if (node->context) redisFree(node->context); if (node->context) redisFree(node->context);
node->context = redisConnect(node->ip, node->port); struct timeval tv;
tv.tv_sec = config.cluster_manager_command.timeout / 1000;
tv.tv_usec = (config.cluster_manager_command.timeout % 1000) * 1000;
node->context = redisConnectWithTimeout(node->ip, node->port, tv);
if (!node->context->err && config.tls) { if (!node->context->err && config.tls) {
const char *err = NULL; const char *err = NULL;
if (cliSecureConnection(node->context, config.sslconfig, &err) == REDIS_ERR && err) { if (cliSecureConnection(node->context, config.sslconfig, &err) == REDIS_ERR && err) {
@@ -3293,8 +3299,8 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
argv_len = zcalloc(argc * sizeof(size_t), MALLOC_LOCAL); argv_len = zcalloc(argc * sizeof(size_t), MALLOC_LOCAL);
char portstr[255]; char portstr[255];
char timeoutstr[255]; char timeoutstr[255];
snprintf(portstr, 10, "%d", target->port); snprintf(portstr, sizeof(portstr), "%d", target->port);
snprintf(timeoutstr, 10, "%d", timeout); snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout);
argv[0] = "MIGRATE"; argv[0] = "MIGRATE";
argv_len[0] = 7; argv_len[0] = 7;
argv[1] = target->ip; argv[1] = target->ip;
@@ -3993,12 +3999,13 @@ cleanup:
return signature; return signature;
} }
int clusterManagerIsConfigConsistent(void) { int clusterManagerIsConfigConsistent(int fLog) {
if (cluster_manager.nodes == NULL) return 0; if (cluster_manager.nodes == NULL) return 0;
int consistent = (listLength(cluster_manager.nodes) <= 1); int consistent = (listLength(cluster_manager.nodes) <= 1);
// If the Cluster has only one node, it's always consistent // If the Cluster has only one node, it's always consistent
if (consistent) return 1; if (consistent) return 1;
sds first_cfg = NULL; sds first_cfg = NULL;
const char *firstNode = NULL;
listIter li; listIter li;
listNode *ln; listNode *ln;
listRewind(cluster_manager.nodes, &li); listRewind(cluster_manager.nodes, &li);
@@ -4009,10 +4016,14 @@ int clusterManagerIsConfigConsistent(void) {
consistent = 0; consistent = 0;
break; break;
} }
if (first_cfg == NULL) first_cfg = cfg; if (first_cfg == NULL) {
else { first_cfg = cfg;
firstNode = node->name;
} else {
consistent = !sdscmp(first_cfg, cfg); consistent = !sdscmp(first_cfg, cfg);
sdsfree(cfg); sdsfree(cfg);
if (fLog && !consistent)
clusterManagerLogInfo("\tNode %s (%s:%d) is inconsistent with %s\n", node->name, node->ip, node->port, firstNode);
if (!consistent) break; if (!consistent) break;
} }
} }
@@ -5161,7 +5172,7 @@ static int clusterManagerCommandReshard(int argc, char **argv) {
clusterManagerNode *node = clusterManagerNewNode(ip, port); clusterManagerNode *node = clusterManagerNewNode(ip, port);
if (!clusterManagerLoadInfoFromNode(node, 0)) return 0; if (!clusterManagerLoadInfoFromNode(node, 0)) return 0;
clusterManagerCheckCluster(0); clusterManagerCheckCluster(0);
if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) { if (cluster_manager.errors && listLength(cluster_manager.errors) > 0 && !config.force_mode) {
fflush(stdout); fflush(stdout);
fprintf(stderr, fprintf(stderr,
"*** Please fix your cluster problems before resharding\n"); "*** Please fix your cluster problems before resharding\n");
@@ -5394,7 +5405,7 @@ static int clusterManagerCommandRebalance(int argc, char **argv) {
if (weightedNodes == NULL) goto cleanup; if (weightedNodes == NULL) goto cleanup;
/* Check cluster, only proceed if it looks sane. */ /* Check cluster, only proceed if it looks sane. */
clusterManagerCheckCluster(1); clusterManagerCheckCluster(1);
if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) { if (cluster_manager.errors && listLength(cluster_manager.errors) > 0 && !config.force_mode) {
clusterManagerLogErr("*** Please fix your cluster problems " clusterManagerLogErr("*** Please fix your cluster problems "
"before rebalancing\n"); "before rebalancing\n");
result = 0; result = 0;
@@ -5618,7 +5629,10 @@ static int clusterManagerCommandImport(int argc, char **argv) {
char *reply_err = NULL; char *reply_err = NULL;
redisReply *src_reply = NULL; redisReply *src_reply = NULL;
// Connect to the source node. // Connect to the source node.
redisContext *src_ctx = redisConnect(src_ip, src_port); struct timeval tv;
tv.tv_sec = config.cluster_manager_command.timeout / 1000;
tv.tv_usec = (config.cluster_manager_command.timeout % 1000) * 1000;
redisContext *src_ctx = redisConnectWithTimeout(src_ip, src_port, tv);
if (src_ctx->err) { if (src_ctx->err) {
success = 0; success = 0;
fprintf(stderr,"Could not connect to KeyDB at %s:%d: %s.\n", src_ip, fprintf(stderr,"Could not connect to KeyDB at %s:%d: %s.\n", src_ip,
@@ -6825,7 +6839,7 @@ static long getLongInfoField(char *info, char *field) {
/* Convert number of bytes into a human readable string of the form: /* Convert number of bytes into a human readable string of the form:
* 100B, 2G, 100M, 4K, and so forth. */ * 100B, 2G, 100M, 4K, and so forth. */
void bytesToHuman(char *s, long long n) { void bytesToHuman(char *s, long long n, size_t bufsize) {
double d; double d;
if (n < 0) { if (n < 0) {
@@ -6835,17 +6849,17 @@ void bytesToHuman(char *s, long long n) {
} }
if (n < 1024) { if (n < 1024) {
/* Bytes */ /* Bytes */
snprintf(s,256,"%lldB",n); snprintf(s,bufsize,"%lldB",n);
return; return;
} else if (n < (1024*1024)) { } else if (n < (1024*1024)) {
d = (double)n/(1024); d = (double)n/(1024);
snprintf(s,256,"%.2fK",d); snprintf(s,bufsize,"%.2fK",d);
} else if (n < (1024LL*1024*1024)) { } else if (n < (1024LL*1024*1024)) {
d = (double)n/(1024*1024); d = (double)n/(1024*1024);
snprintf(s,256,"%.2fM",d); snprintf(s,bufsize,"%.2fM",d);
} else if (n < (1024LL*1024*1024*1024)) { } else if (n < (1024LL*1024*1024*1024)) {
d = (double)n/(1024LL*1024*1024); d = (double)n/(1024LL*1024*1024);
snprintf(s,256,"%.2fG",d); snprintf(s,bufsize,"%.2fG",d);
} }
} }
@@ -6875,38 +6889,38 @@ static void statMode(void) {
for (j = 0; j < 20; j++) { for (j = 0; j < 20; j++) {
long k; long k;
snprintf(buf,64,"db%d:keys",j); snprintf(buf,sizeof(buf),"db%d:keys",j);
k = getLongInfoField(reply->str,buf); k = getLongInfoField(reply->str,buf);
if (k == LONG_MIN) continue; if (k == LONG_MIN) continue;
aux += k; aux += k;
} }
snprintf(buf,64,"%ld",aux); snprintf(buf,sizeof(buf),"%ld",aux);
printf("%-11s",buf); printf("%-11s",buf);
/* Used memory */ /* Used memory */
aux = getLongInfoField(reply->str,"used_memory"); aux = getLongInfoField(reply->str,"used_memory");
bytesToHuman(buf,aux); bytesToHuman(buf,aux,sizeof(buf));
printf("%-8s",buf); printf("%-8s",buf);
/* Clients */ /* Clients */
aux = getLongInfoField(reply->str,"connected_clients"); aux = getLongInfoField(reply->str,"connected_clients");
snprintf(buf,64,"%ld",aux); snprintf(buf,sizeof(buf),"%ld",aux);
printf(" %-8s",buf); printf(" %-8s",buf);
/* Blocked (BLPOPPING) Clients */ /* Blocked (BLPOPPING) Clients */
aux = getLongInfoField(reply->str,"blocked_clients"); aux = getLongInfoField(reply->str,"blocked_clients");
snprintf(buf,64,"%ld",aux); snprintf(buf,sizeof(buf),"%ld",aux);
printf("%-8s",buf); printf("%-8s",buf);
/* Requests */ /* Requests */
aux = getLongInfoField(reply->str,"total_commands_processed"); aux = getLongInfoField(reply->str,"total_commands_processed");
snprintf(buf,64,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests); snprintf(buf,sizeof(buf),"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
printf("%-19s",buf); printf("%-19s",buf);
requests = aux; requests = aux;
/* Connections */ /* Connections */
aux = getLongInfoField(reply->str,"total_connections_received"); aux = getLongInfoField(reply->str,"total_connections_received");
snprintf(buf,64,"%ld",aux); snprintf(buf,sizeof(buf),"%ld",aux);
printf(" %-12s",buf); printf(" %-12s",buf);
/* Children */ /* Children */
@@ -7185,6 +7199,7 @@ int main(int argc, char **argv) {
config.set_errcode = 0; config.set_errcode = 0;
config.no_auth_warning = 0; config.no_auth_warning = 0;
config.in_multi = 0; config.in_multi = 0;
config.force_mode = 0;
config.cluster_manager_command.name = NULL; config.cluster_manager_command.name = NULL;
config.cluster_manager_command.argc = 0; config.cluster_manager_command.argc = 0;
config.cluster_manager_command.argv = NULL; config.cluster_manager_command.argv = NULL;
+3 -2
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "cli_common.h" #include "cli_common.h"
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */ #include "sdscompat.h" /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
#include <sds.h> #include <sds.h>
#ifdef __cplusplus #ifdef __cplusplus
@@ -195,6 +195,7 @@ extern struct config {
int in_multi; int in_multi;
int pre_multi_dbnum; int pre_multi_dbnum;
int quoted_input; /* Force input args to be treated as quoted strings */ int quoted_input; /* Force input args to be treated as quoted strings */
int force_mode;
} config; } config;
struct clusterManager { struct clusterManager {
@@ -282,7 +283,7 @@ int clusterManagerFixOpenSlot(int slot);
void clusterManagerPrintSlotsList(list *slots); void clusterManagerPrintSlotsList(list *slots);
int clusterManagerGetCoveredSlots(char *all_slots); int clusterManagerGetCoveredSlots(char *all_slots);
void clusterManagerOnError(sds err); void clusterManagerOnError(sds err);
int clusterManagerIsConfigConsistent(void); int clusterManagerIsConfigConsistent(int fLog);
void freeClusterManagerNode(clusterManagerNode *node); void freeClusterManagerNode(clusterManagerNode *node);
void clusterManagerLog(int level, const char* fmt, ...); void clusterManagerLog(int level, const char* fmt, ...);
int parseClusterNodeAddress(char *addr, char **ip_ptr, int *port_ptr, int parseClusterNodeAddress(char *addr, char **ip_ptr, int *port_ptr,
+1
View File
@@ -326,6 +326,7 @@ static const RedisModuleEvent
#define REDISMODULE_SUBEVENT_LOADING_ENDED 3 #define REDISMODULE_SUBEVENT_LOADING_ENDED 3
#define REDISMODULE_SUBEVENT_LOADING_FAILED 4 #define REDISMODULE_SUBEVENT_LOADING_FAILED 4
#define _REDISMODULE_SUBEVENT_LOADING_NEXT 5 #define _REDISMODULE_SUBEVENT_LOADING_NEXT 5
#define REDISMODULE_SUBEVENT_LOADING_FLASH_START 6
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0 #define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1 #define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1
+80 -59
View File
@@ -188,6 +188,8 @@ int bg_unlink(const char *filename) {
/* ---------------------------------- MASTER -------------------------------- */ /* ---------------------------------- MASTER -------------------------------- */
bool createDiskBacklog() { bool createDiskBacklog() {
if (g_pserver->repl_backlog_disk != nullptr)
return true; // already exists
// Lets create some disk backed pages and add them here // Lets create some disk backed pages and add them here
std::string path = "./repl-backlog-temp" + std::to_string(gettid()); std::string path = "./repl-backlog-temp" + std::to_string(gettid());
#if (defined __APPLE__ || defined __FreeBSD__) #if (defined __APPLE__ || defined __FreeBSD__)
@@ -345,7 +347,8 @@ void freeReplicationBacklog(void) {
client *c = (client*)listNodeValue(ln); client *c = (client*)listNodeValue(ln);
serverAssert(c->flags & CLIENT_CLOSE_ASAP || FMasterHost(c)); serverAssert(c->flags & CLIENT_CLOSE_ASAP || FMasterHost(c));
} }
zfree(g_pserver->repl_backlog); if (g_pserver->repl_backlog != g_pserver->repl_backlog_disk)
zfree(g_pserver->repl_backlog);
g_pserver->repl_backlog = NULL; g_pserver->repl_backlog = NULL;
} }
@@ -410,7 +413,7 @@ void feedReplicationBacklog(const void *ptr, size_t len) {
if (minimumsize > g_pserver->repl_backlog_size && listening_replicas) { if (minimumsize > g_pserver->repl_backlog_size && listening_replicas) {
// This is an emergency overflow, we better resize to fit // This is an emergency overflow, we better resize to fit
long long newsize = std::max(g_pserver->repl_backlog_size*2, minimumsize); long long newsize = std::max(g_pserver->repl_backlog_size*2, minimumsize);
serverLog(LL_WARNING, "Replication backlog is too small, resizing to: %lld bytes", newsize); serverLog(LL_WARNING, "Replication backlog is too small, resizing from %lld to %lld bytes", g_pserver->repl_backlog_size, newsize);
resizeReplicationBacklog(newsize); resizeReplicationBacklog(newsize);
} else if (!listening_replicas) { } else if (!listening_replicas) {
// We need to update a few variables or later asserts will notice we dropped data // We need to update a few variables or later asserts will notice we dropped data
@@ -1016,8 +1019,6 @@ public:
void addReplica(client *replica) { void addReplica(client *replica) {
replicas.push_back(replica); replicas.push_back(replica);
replicationSetupSlaveForFullResync(replica,getPsyncInitialOffset()); replicationSetupSlaveForFullResync(replica,getPsyncInitialOffset());
// Optimize the socket for bulk transfer
//connDisableTcpNoDelay(replica->conn);
} }
bool isActive() const { return !replicas.empty(); } bool isActive() const { return !replicas.empty(); }
@@ -1090,7 +1091,7 @@ public:
reply->used += size; reply->used += size;
} }
void addLongWithPrefix(long val, char prefix) { void addLongLongWithPrefix(long long val, char prefix) {
char buf[128]; char buf[128];
int len; int len;
@@ -1102,15 +1103,19 @@ public:
} }
void addArrayLen(int len) { void addArrayLen(int len) {
addLongWithPrefix(len, '*'); addLongLongWithPrefix(len, '*');
} }
void addLong(long val) { void addLong(long val) {
addLongWithPrefix(val, ':'); addLongLongWithPrefix(val, ':');
}
void addLongLong(long long val) {
addLongLongWithPrefix(val, ':');
} }
void addString(const char *s, unsigned long len) { void addString(const char *s, unsigned long len) {
addLongWithPrefix(len, '$'); addLongLongWithPrefix(len, '$');
addData(s, len); addData(s, len);
addData("\r\n", 2); addData("\r\n", 2);
} }
@@ -1146,22 +1151,19 @@ int rdbSaveSnapshotForReplication(rdbSaveInfo *rsi) {
// TODO: This needs to be on a background thread // TODO: This needs to be on a background thread
int retval = C_OK; int retval = C_OK;
serverAssert(GlobalLocksAcquired()); serverAssert(GlobalLocksAcquired());
serverLog(LL_NOTICE, "Starting storage provider fast full sync with target: %s", "disk"); serverLog(LL_NOTICE, "Starting fast full sync with target: %s", "disk");
std::shared_ptr<replicationBuffer> spreplBuf = std::make_shared<replicationBuffer>(); std::shared_ptr<replicationBuffer> spreplBuf = std::make_shared<replicationBuffer>();
listNode *ln; listNode *ln;
listIter li; listIter li;
client *replica = nullptr;
listRewind(g_pserver->slaves, &li); listRewind(g_pserver->slaves, &li);
while (replica == nullptr && (ln = listNext(&li))) { while ((ln = listNext(&li))) {
client *replicaCur = (client*)listNodeValue(ln); client *replicaCur = (client*)listNodeValue(ln);
if ((replicaCur->slave_capa & SLAVE_CAPA_ROCKSDB_SNAPSHOT) && (replicaCur->replstate == SLAVE_STATE_WAIT_BGSAVE_START)) { if ((replicaCur->slave_capa & SLAVE_CAPA_KEYDB_FASTSYNC) && (replicaCur->replstate == SLAVE_STATE_WAIT_BGSAVE_START)) {
replica = replicaCur; spreplBuf->addReplica(replicaCur);
spreplBuf->addReplica(replica);
replicaCur->replstate = SLAVE_STATE_FASTSYNC_TX; replicaCur->replstate = SLAVE_STATE_FASTSYNC_TX;
} }
} }
serverAssert(replica != nullptr);
spreplBuf->addArrayLen(2); // Two sections: Metadata and databases spreplBuf->addArrayLen(2); // Two sections: Metadata and databases
@@ -1176,7 +1178,7 @@ int rdbSaveSnapshotForReplication(rdbSaveInfo *rsi) {
spreplBuf->addString(rsi->repl_id, CONFIG_RUN_ID_SIZE); spreplBuf->addString(rsi->repl_id, CONFIG_RUN_ID_SIZE);
spreplBuf->addArrayLen(2); spreplBuf->addArrayLen(2);
spreplBuf->addString("repl-offset", 11); spreplBuf->addString("repl-offset", 11);
spreplBuf->addLong(rsi->master_repl_offset); spreplBuf->addLongLong(rsi->master_repl_offset);
if (dictSize(g_pserver->lua_scripts)) { if (dictSize(g_pserver->lua_scripts)) {
dictEntry *de; dictEntry *de;
@@ -1192,13 +1194,13 @@ int rdbSaveSnapshotForReplication(rdbSaveInfo *rsi) {
di = NULL; /* So that we don't release it again on error. */ di = NULL; /* So that we don't release it again on error. */
} }
std::shared_ptr<std::vector<std::unique_ptr<const StorageCache>>> spvecspsnapshot = std::make_shared<std::vector<std::unique_ptr<const StorageCache>>>(); std::shared_ptr<std::vector<const redisDbPersistentDataSnapshot*>> spvecsnapshot = std::make_shared<std::vector<const redisDbPersistentDataSnapshot*>>();
for (int idb = 0; idb < cserver.dbnum; ++idb) { for (int idb = 0; idb < cserver.dbnum; ++idb) {
spvecspsnapshot->emplace_back(g_pserver->db[idb]->CloneStorageCache()); spvecsnapshot->emplace_back(g_pserver->db[idb]->createSnapshot(getMvccTstamp(), false));
} }
aeReleaseLock(); aeReleaseLock();
g_pserver->asyncworkqueue->AddWorkFunction([spreplBuf = std::move(spreplBuf), spvecspsnapshot = std::move(spvecspsnapshot)]{ g_pserver->asyncworkqueue->AddWorkFunction([spreplBuf = std::move(spreplBuf), spvecsnapshot = std::move(spvecsnapshot)]{
int retval = C_OK; int retval = C_OK;
auto timeStart = ustime(); auto timeStart = ustime();
auto lastLogTime = timeStart; auto lastLogTime = timeStart;
@@ -1208,15 +1210,16 @@ int rdbSaveSnapshotForReplication(rdbSaveInfo *rsi) {
// Databases // Databases
replBuf.addArrayLen(cserver.dbnum); replBuf.addArrayLen(cserver.dbnum);
for (int idb = 0; idb < cserver.dbnum; ++idb) { for (int idb = 0; idb < cserver.dbnum; ++idb) {
auto &spsnapshot = (*spvecspsnapshot)[idb]; auto &spsnapshot = (*spvecsnapshot)[idb];
size_t snapshotDeclaredCount = spsnapshot->count(); size_t snapshotDeclaredCount = spsnapshot->size();
replBuf.addArrayLen(snapshotDeclaredCount); replBuf.addArrayLen(snapshotDeclaredCount);
size_t count = 0; size_t count = 0;
bool result = spsnapshot->enumerate([&replBuf, &count, &cbData, &lastLogTime, &cbLastUpdate](const char *rgchKey, size_t cchKey, const void *rgchVal, size_t cchVal) -> bool{ bool result = spsnapshot->iterate_threadsafe([&replBuf, &count, &cbData, &lastLogTime, &cbLastUpdate](const char *strKey, robj_roptr o) -> bool{
replBuf.addArrayLen(2); replBuf.addArrayLen(2);
replBuf.addString(rgchKey, cchKey); replBuf.addString(strKey, sdslen(strKey));
replBuf.addString((const char *)rgchVal, cchVal); sds strT = serializeStoredObjectAndExpire(o);
replBuf.addString(strT, sdslen(strT));
++count; ++count;
if ((count % 8092) == 0) { if ((count % 8092) == 0) {
auto curTime = ustime(); auto curTime = ustime();
@@ -1227,7 +1230,8 @@ int rdbSaveSnapshotForReplication(rdbSaveInfo *rsi) {
lastLogTime = ustime(); lastLogTime = ustime();
} }
} }
cbData += cchKey + cchVal; cbData += sdslen(strKey) + sdslen(strT);
sdsfree(strT);
return replBuf.isActive(); return replBuf.isActive();
}); });
@@ -1299,7 +1303,7 @@ int startBgsaveForReplication(int mincapa) {
/* Only do rdbSave* when rsiptr is not NULL, /* Only do rdbSave* when rsiptr is not NULL,
* otherwise replica will miss repl-stream-db. */ * otherwise replica will miss repl-stream-db. */
if (rsiptr) { if (rsiptr) {
if (mincapa & SLAVE_CAPA_ROCKSDB_SNAPSHOT && g_pserver->m_pstorageFactory) if (mincapa & SLAVE_CAPA_KEYDB_FASTSYNC && FFastSyncEnabled())
retval = rdbSaveSnapshotForReplication(rsiptr); retval = rdbSaveSnapshotForReplication(rsiptr);
else if (socket_target) else if (socket_target)
retval = rdbSaveToSlavesSockets(rsiptr); retval = rdbSaveToSlavesSockets(rsiptr);
@@ -1478,7 +1482,7 @@ void syncCommand(client *c) {
} }
/* CASE 0: Fast Sync */ /* CASE 0: Fast Sync */
if ((c->slave_capa & SLAVE_CAPA_ROCKSDB_SNAPSHOT) && g_pserver->m_pstorageFactory) { if (c->slave_capa & SLAVE_CAPA_KEYDB_FASTSYNC && FFastSyncEnabled()) {
serverLog(LL_NOTICE,"Fast SYNC on next replication cycle"); serverLog(LL_NOTICE,"Fast SYNC on next replication cycle");
/* CASE 1: BGSAVE is in progress, with disk target. */ /* CASE 1: BGSAVE is in progress, with disk target. */
} else if (g_pserver->FRdbSaveInProgress() && } else if (g_pserver->FRdbSaveInProgress() &&
@@ -1538,7 +1542,7 @@ void syncCommand(client *c) {
} else { } else {
/* We don't have a BGSAVE in progress, let's start one. Diskless /* We don't have a BGSAVE in progress, let's start one. Diskless
* or disk-based mode is determined by replica's capacity. */ * or disk-based mode is determined by replica's capacity. */
if (!hasActiveChildProcess()) { if (!hasActiveChildProcessOrBGSave()) {
startBgsaveForReplication(c->slave_capa); startBgsaveForReplication(c->slave_capa);
} else { } else {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
@@ -1669,8 +1673,8 @@ void replconfCommand(client *c) {
c->slave_capa |= SLAVE_CAPA_PSYNC2; c->slave_capa |= SLAVE_CAPA_PSYNC2;
else if (!strcasecmp((const char*)ptrFromObj(c->argv[j+1]), "activeExpire")) else if (!strcasecmp((const char*)ptrFromObj(c->argv[j+1]), "activeExpire"))
c->slave_capa |= SLAVE_CAPA_ACTIVE_EXPIRE; c->slave_capa |= SLAVE_CAPA_ACTIVE_EXPIRE;
else if (!strcasecmp((const char*)ptrFromObj(c->argv[j+1]), "rocksdb-snapshot-load")) else if (!strcasecmp((const char*)ptrFromObj(c->argv[j+1]), "keydb-fastsync"))
c->slave_capa |= SLAVE_CAPA_ROCKSDB_SNAPSHOT; c->slave_capa |= SLAVE_CAPA_KEYDB_FASTSYNC;
fCapaCommand = true; fCapaCommand = true;
} else if (!strcasecmp((const char*)ptrFromObj(c->argv[j]),"ack")) { } else if (!strcasecmp((const char*)ptrFromObj(c->argv[j]),"ack")) {
@@ -1735,10 +1739,14 @@ void replconfCommand(client *c) {
if (fCapaCommand) { if (fCapaCommand) {
sds reply = sdsnew("+OK"); sds reply = sdsnew("+OK");
if (g_pserver->fActiveReplica) if (g_pserver->fActiveReplica) {
reply = sdscat(reply, " active-replica"); reply = sdscat(reply, " active-replica");
if (g_pserver->m_pstorageFactory && (c->slave_capa & SLAVE_CAPA_ROCKSDB_SNAPSHOT) && !g_pserver->fActiveReplica) }
reply = sdscat(reply, " rocksdb-snapshot-save"); if ((c->slave_capa & SLAVE_CAPA_KEYDB_FASTSYNC) && FFastSyncEnabled()) {
reply = sdscat(reply, " keydb-fastsync-save");
} else {
c->slave_capa = (c->slave_capa & (~SLAVE_CAPA_KEYDB_FASTSYNC)); // never try to fast sync for this as they won't expect it
}
reply = sdscat(reply, "\r\n"); reply = sdscat(reply, "\r\n");
addReplySds(c, reply); addReplySds(c, reply);
} else { } else {
@@ -2512,6 +2520,7 @@ bool readSnapshotBulkPayload(connection *conn, redisMaster *mi, rdbSaveInfo &rsi
} }
} }
serverAssert(mi->parseState != nullptr);
for (int iter = 0; iter < 10; ++iter) { for (int iter = 0; iter < 10; ++iter) {
if (mi->parseState->shouldThrottle()) if (mi->parseState->shouldThrottle())
return false; return false;
@@ -2522,11 +2531,16 @@ bool readSnapshotBulkPayload(connection *conn, redisMaster *mi, rdbSaveInfo &rsi
auto nread = connRead(conn, mi->bulkreadBuffer+qblen, readlen); auto nread = connRead(conn, mi->bulkreadBuffer+qblen, readlen);
if (nread <= 0) { if (nread <= 0) {
if (connGetState(conn) != CONN_STATE_CONNECTED) if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
(nread == -1) ? strerror(errno) : "connection lost");
cancelReplicationHandshake(mi, true); cancelReplicationHandshake(mi, true);
}
return false; return false;
} }
g_pserver->stat_net_input_bytes += nread;
mi->repl_transfer_lastio = g_pserver->unixtime; mi->repl_transfer_lastio = g_pserver->unixtime;
mi->repl_transfer_read += nread;
sdsIncrLen(mi->bulkreadBuffer,nread); sdsIncrLen(mi->bulkreadBuffer,nread);
size_t offset = 0; size_t offset = 0;
@@ -2594,12 +2608,16 @@ bool readSnapshotBulkPayload(connection *conn, redisMaster *mi, rdbSaveInfo &rsi
if (mi->parseState->depth() != 0) if (mi->parseState->depth() != 0)
return false; return false;
static_assert(sizeof(long) == sizeof(long long),""); long long repl_stream_db = mi->parseState->getMetaDataLongLong("repl-stream-db");
rsi.repl_stream_db = mi->parseState->getMetaDataLongLong("repl-stream-db"); if (repl_stream_db < INT_MIN || repl_stream_db > INT_MAX) {
throw "Invalid repl_stream_db value";
}
rsi.repl_stream_db = static_cast<int>(repl_stream_db);
rsi.repl_offset = mi->parseState->getMetaDataLongLong("repl-offset"); rsi.repl_offset = mi->parseState->getMetaDataLongLong("repl-offset");
sds str = mi->parseState->getMetaDataStr("repl-id"); sds str = mi->parseState->getMetaDataStr("repl-id");
if (sdslen(str) == CONFIG_RUN_ID_SIZE+1) { if (sdslen(str) == CONFIG_RUN_ID_SIZE) {
memcpy(rsi.repl_id, str, CONFIG_RUN_ID_SIZE+1); memcpy(rsi.repl_id, str, CONFIG_RUN_ID_SIZE+1);
rsi.repl_id_is_set = 1;
} }
fFinished = true; fFinished = true;
@@ -2998,7 +3016,7 @@ void readSyncBulkPayload(connection *conn) {
return; return;
} }
if (mi->isRocksdbSnapshotRepl) { if (mi->isKeydbFastsync) {
if (!readSnapshotBulkPayload(conn, mi, rsi)) if (!readSnapshotBulkPayload(conn, mi, rsi))
return; return;
} else { } else {
@@ -3011,17 +3029,15 @@ void readSyncBulkPayload(connection *conn) {
/* Final setup of the connected slave <- master link */ /* Final setup of the connected slave <- master link */
replicationCreateMasterClient(mi,mi->repl_transfer_s,rsi.repl_stream_db); replicationCreateMasterClient(mi,mi->repl_transfer_s,rsi.repl_stream_db);
if (mi->isRocksdbSnapshotRepl) { if (mi->isKeydbFastsync) {
/* We need to handle the case where the initial querybuf data was read by fast sync */ /* We need to handle the case where the initial querybuf data was read by fast sync */
/* This should match the work readQueryFromClient would do for a master client */ /* This should match the work readQueryFromClient would do for a master client */
mi->master->querybuf = sdscatsds(mi->master->querybuf, mi->bulkreadBuffer); mi->master->querybuf = sdscatsds(mi->master->querybuf, mi->bulkreadBuffer);
mi->master->pending_querybuf = sdscatsds(mi->master->pending_querybuf, mi->bulkreadBuffer);
mi->master->read_reploff += sdslen(mi->bulkreadBuffer);
sdsfree(mi->bulkreadBuffer); sdsfree(mi->bulkreadBuffer);
mi->bulkreadBuffer = nullptr; mi->bulkreadBuffer = nullptr;
mi->master->pending_querybuf = sdscatlen(mi->master->pending_querybuf,
mi->master->querybuf,sdslen(mi->master->querybuf));
mi->master->read_reploff += sdslen(mi->master->querybuf);
} }
mi->repl_transfer_s = nullptr; mi->repl_transfer_s = nullptr;
mi->repl_state = REPL_STATE_CONNECTED; mi->repl_state = REPL_STATE_CONNECTED;
@@ -3060,13 +3076,13 @@ void readSyncBulkPayload(connection *conn) {
} }
/* Send the initial ACK immediately to put this replica in online state. */ /* Send the initial ACK immediately to put this replica in online state. */
if (usemark || mi->isRocksdbSnapshotRepl) replicationSendAck(mi); if (usemark || mi->isKeydbFastsync) replicationSendAck(mi);
/* Restart the AOF subsystem now that we finished the sync. This /* Restart the AOF subsystem now that we finished the sync. This
* will trigger an AOF rewrite, and when done will start appending * will trigger an AOF rewrite, and when done will start appending
* to the new file. */ * to the new file. */
if (g_pserver->aof_enabled) restartAOFAfterSYNC(); if (g_pserver->aof_enabled) restartAOFAfterSYNC();
if (mi->isRocksdbSnapshotRepl) if (mi->isKeydbFastsync)
readQueryFromClient(conn); // There may be querybuf data we just appeneded readQueryFromClient(conn); // There may be querybuf data we just appeneded
return; return;
} }
@@ -3392,15 +3408,15 @@ void parseMasterCapa(redisMaster *mi, sds strcapa)
char *pchEnd = szStart; char *pchEnd = szStart;
mi->isActive = false; mi->isActive = false;
mi->isRocksdbSnapshotRepl = false; mi->isKeydbFastsync = false;
for (;;) for (;;)
{ {
if (*pchEnd == ' ' || *pchEnd == '\0') { if (*pchEnd == ' ' || *pchEnd == '\0') {
// Parse the word // Parse the word
if (strncmp(szStart, "active-replica", pchEnd - szStart) == 0) { if (strncmp(szStart, "active-replica", pchEnd - szStart) == 0) {
mi->isActive = true; mi->isActive = true;
} else if (strncmp(szStart, "rocksdb-snapshot-save", pchEnd - szStart) == 0) { } else if (strncmp(szStart, "keydb-fastsync-save", pchEnd - szStart) == 0) {
mi->isRocksdbSnapshotRepl = true; mi->isKeydbFastsync = true;
} }
szStart = pchEnd + 1; szStart = pchEnd + 1;
} }
@@ -3547,9 +3563,9 @@ retry_connect:
"capa","psync2", "capa","psync2",
"capa","activeExpire", "capa","activeExpire",
}; };
if (g_pserver->m_pstorageFactory && !g_pserver->fActiveReplica && g_pserver->repl_diskless_load != REPL_DISKLESS_LOAD_SWAPDB) { if (FFastSyncEnabled() && g_pserver->repl_diskless_load != REPL_DISKLESS_LOAD_SWAPDB) {
veccapabilities.push_back("capa"); veccapabilities.push_back("capa");
veccapabilities.push_back("rocksdb-snapshot-load"); veccapabilities.push_back("keydb-fastsync");
} }
err = sendCommandArgv(conn, veccapabilities.size(), veccapabilities.data(), nullptr); err = sendCommandArgv(conn, veccapabilities.size(), veccapabilities.data(), nullptr);
@@ -3732,12 +3748,12 @@ retry_connect:
} }
/* Prepare a suitable temp file for bulk transfer */ /* Prepare a suitable temp file for bulk transfer */
if (!useDisklessLoad()) { if (!useDisklessLoad() && !mi->isKeydbFastsync) {
while(maxtries--) { while(maxtries--) {
auto dt = std::chrono::system_clock::now().time_since_epoch(); auto dt = std::chrono::system_clock::now().time_since_epoch();
auto dtMillisecond = std::chrono::duration_cast<std::chrono::milliseconds>(dt); auto dtMillisecond = std::chrono::duration_cast<std::chrono::milliseconds>(dt);
snprintf(tmpfile,256, snprintf(tmpfile,sizeof(tmpfile),
"temp-%d.%ld.rdb",(int)dtMillisecond.count(),(long int)getpid()); "temp-%lld.%ld.rdb",(long long)dtMillisecond.count(),(long int)getpid());
dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644); dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
if (dfd != -1) break; if (dfd != -1) break;
sleep(1); sleep(1);
@@ -3797,9 +3813,11 @@ int connectWithMaster(redisMaster *mi) {
connSetPrivateData(mi->repl_transfer_s, mi); connSetPrivateData(mi->repl_transfer_s, mi);
if (connConnect(mi->repl_transfer_s, mi->masterhost, mi->masterport, if (connConnect(mi->repl_transfer_s, mi->masterhost, mi->masterport,
NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) { NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
int sev = g_pserver->enable_multimaster ? LL_NOTICE : LL_WARNING; // with multimaster its not unheard of to intentiallionall have downed masters const char *err = "Unknown Error";
serverLog(sev,"Unable to connect to MASTER: %s", if (mi->repl_transfer_s->last_errno != 0)
connGetLastError(mi->repl_transfer_s)); err = connGetLastError(mi->repl_transfer_s);
int sev = g_pserver->enable_multimaster ? LL_NOTICE : LL_WARNING; // with multimaster its not unheard of to intentionally have downed masters
serverLog(sev, "Unable to connect to MASTER: %s", err);
connClose(mi->repl_transfer_s); connClose(mi->repl_transfer_s);
mi->repl_transfer_s = NULL; mi->repl_transfer_s = NULL;
return C_ERR; return C_ERR;
@@ -3859,6 +3877,10 @@ int cancelReplicationHandshake(redisMaster *mi, int reconnect) {
delete mi->parseState; delete mi->parseState;
mi->parseState = nullptr; mi->parseState = nullptr;
} }
if (mi->bulkreadBuffer) {
sdsfree(mi->bulkreadBuffer);
mi->bulkreadBuffer = nullptr;
}
if (mi->repl_state == REPL_STATE_TRANSFER) { if (mi->repl_state == REPL_STATE_TRANSFER) {
replicationAbortSyncTransfer(mi); replicationAbortSyncTransfer(mi);
@@ -4324,7 +4346,6 @@ void replicationCacheMaster(redisMaster *mi, client *c) {
if (c->flags & CLIENT_MULTI) discardTransaction(c); if (c->flags & CLIENT_MULTI) discardTransaction(c);
listEmpty(c->reply); listEmpty(c->reply);
c->sentlen = 0; c->sentlen = 0;
c->sentlenAsync = 0;
c->reply_bytes = 0; c->reply_bytes = 0;
c->bufpos = 0; c->bufpos = 0;
resetClient(c); resetClient(c);
@@ -4984,7 +5005,7 @@ void replicationStartPendingFork(void) {
* In case of diskless replication, we make sure to wait the specified * In case of diskless replication, we make sure to wait the specified
* number of seconds (according to configuration) so that other slaves * number of seconds (according to configuration) so that other slaves
* have the time to arrive before we start streaming. */ * have the time to arrive before we start streaming. */
if (!hasActiveChildProcess()) { if (!hasActiveChildProcessOrBGSave()) {
time_t idle, max_idle = 0; time_t idle, max_idle = 0;
int slaves_waiting = 0; int slaves_waiting = 0;
int mincapa = -1; int mincapa = -1;
+3
View File
@@ -1264,6 +1264,9 @@ void scriptingInit(int setup) {
/* Finally set the table as 'redis' global var. */ /* Finally set the table as 'redis' global var. */
lua_setglobal(lua,"redis"); lua_setglobal(lua,"redis");
/* Set table as 'keydb' global var as well */
lua_getglobal(lua,"redis");
lua_setglobal(lua,"keydb");
/* Replace math.random and math.randomseed with our implementations. */ /* Replace math.random and math.randomseed with our implementations. */
lua_getglobal(lua,"math"); lua_getglobal(lua,"math");
+4 -1
View File
@@ -418,7 +418,10 @@ public:
sdsstring &operator=(const sdsstring &other) sdsstring &operator=(const sdsstring &other)
{ {
sdsfree(m_str); sdsfree(m_str);
m_str = sdsdup(other.m_str); if (other.m_str != nullptr)
m_str = sdsdup(other.m_str);
else
m_str = nullptr;
return *this; return *this;
} }
@@ -42,6 +42,8 @@
#ifndef HIREDIS_SDS_COMPAT #ifndef HIREDIS_SDS_COMPAT
#define HIREDIS_SDS_COMPAT #define HIREDIS_SDS_COMPAT
#ifndef USE_SYSTEM_HIREDIS
#define sds hisds #define sds hisds
#define sdslen hi_sdslen #define sdslen hi_sdslen
@@ -91,4 +93,6 @@
#define sdsull2str hi_sdsull2str #define sdsull2str hi_sdsull2str
#define sdsupdatelen hi_sdsupdatelen #define sdsupdatelen hi_sdsupdatelen
#endif /* !USE_SYSTEM_HIREDIS */
#endif /* HIREDIS_SDS_COMPAT */ #endif /* HIREDIS_SDS_COMPAT */
+6 -2
View File
@@ -27,6 +27,7 @@ namespace keydbutils
template<> template<>
size_t hash(const sdsview &); size_t hash(const sdsview &);
} }
extern size_t g_semiOrderedSetTargetBucketSize;
template<typename T, typename T_KEY = T, bool MEMMOVE_SAFE = false> template<typename T, typename T_KEY = T, bool MEMMOVE_SAFE = false>
class semiorderedset class semiorderedset
@@ -41,11 +42,14 @@ class semiorderedset
size_t idxRehash = (1ULL << bits_min); size_t idxRehash = (1ULL << bits_min);
int cfPauseRehash = 0; int cfPauseRehash = 0;
constexpr size_t targetElementsPerBucket() inline size_t targetElementsPerBucket()
{ {
// Aim for roughly 4 cache lines per bucket (determined by imperical testing) // Aim for roughly 4 cache lines per bucket (determined by imperical testing)
// lower values are faster but use more memory // lower values are faster but use more memory
return std::max((64/sizeof(T))*8, (size_t)2); if (g_semiOrderedSetTargetBucketSize == 0)
return std::max((64/sizeof(T))*8, (size_t)2);
else
return g_semiOrderedSetTargetBucketSize;
} }
public: public:
+183 -92
View File
@@ -70,6 +70,7 @@
#ifdef __linux__ #ifdef __linux__
#include <sys/prctl.h> #include <sys/prctl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/sysinfo.h>
#endif #endif
int g_fTestMode = false; int g_fTestMode = false;
@@ -1517,6 +1518,16 @@ dictType dbDictType = {
dictGCAsyncFree /* async free destructor */ dictGCAsyncFree /* async free destructor */
}; };
dictType dbExpiresDictType = {
dictSdsHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCompare, /* key compare */
NULL, /* key destructor */
NULL, /* val destructor */
dictExpandAllowed /* allow to expand */
};
/* db->pdict, keys are sds strings, vals are Redis objects. */ /* db->pdict, keys are sds strings, vals are Redis objects. */
dictType dbTombstoneDictType = { dictType dbTombstoneDictType = {
dictSdsHash, /* hash function */ dictSdsHash, /* hash function */
@@ -1549,17 +1560,6 @@ dictType shaScriptObjectDictType = {
NULL /* allow to expand */ NULL /* allow to expand */
}; };
/* Db->expires */
dictType dbExpiresDictType = {
dictSdsHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCompare, /* key compare */
NULL, /* key destructor */
NULL, /* val destructor */
dictExpandAllowed /* allow to expand */
};
/* Command table. sds string -> command struct pointer. */ /* Command table. sds string -> command struct pointer. */
dictType commandTableDictType = { dictType commandTableDictType = {
dictSdsCaseHash, /* hash function */ dictSdsCaseHash, /* hash function */
@@ -1706,7 +1706,7 @@ int redisDbPersistentData::incrementallyRehash() {
* for dict.c to resize the hash tables accordingly to the fact we have an * for dict.c to resize the hash tables accordingly to the fact we have an
* active fork child running. */ * active fork child running. */
void updateDictResizePolicy(void) { void updateDictResizePolicy(void) {
if (!hasActiveChildProcess() || (g_pserver->FRdbSaveInProgress() && !cserver.fForkBgSave)) if (!hasActiveChildProcess())
dictEnableResize(); dictEnableResize();
else else
dictDisableResize(); dictDisableResize();
@@ -1725,7 +1725,11 @@ const char *strChildType(int type) {
/* Return true if there are active children processes doing RDB saving, /* Return true if there are active children processes doing RDB saving,
* AOF rewriting, or some side process spawned by a loaded module. */ * AOF rewriting, or some side process spawned by a loaded module. */
int hasActiveChildProcess() { int hasActiveChildProcess() {
return g_pserver->FRdbSaveInProgress() || g_pserver->child_pid != -1; return g_pserver->child_pid != -1;
}
int hasActiveChildProcessOrBGSave() {
return g_pserver->FRdbSaveInProgress() || hasActiveChildProcess();
} }
void resetChildState() { void resetChildState() {
@@ -1866,6 +1870,9 @@ VersionCompareResult compareVersion(SymVer *pver)
if ((symVerThis.major == 0 && symVerThis.minor == 0 && symVerThis.build == 0) if ((symVerThis.major == 0 && symVerThis.minor == 0 && symVerThis.build == 0)
|| (pver->major == 0 && pver->minor == 0 && pver->build == 0)) || (pver->major == 0 && pver->minor == 0 && pver->build == 0))
return VersionCompareResult::EqualVersion; return VersionCompareResult::EqualVersion;
if (pver->major <= 6 && pver->minor <= 3 && pver->build <= 3)
return VersionCompareResult::IncompatibleVersion;
for (int iver = 0; iver < 3; ++iver) for (int iver = 0; iver < 3; ++iver)
{ {
@@ -1954,6 +1961,16 @@ void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
*out_usage = o; *out_usage = o;
} }
int closeClientOnOverload(client *c) {
if (g_pserver->overload_closed_clients > MAX_CLIENTS_SHED_PER_PERIOD) return false;
if (!g_pserver->is_overloaded) return false;
// Don't close masters, replicas, or pub/sub clients
if (c->flags & (CLIENT_MASTER | CLIENT_SLAVE | CLIENT_PENDING_WRITE | CLIENT_PUBSUB | CLIENT_BLOCKED)) return false;
freeClient(c);
++g_pserver->overload_closed_clients;
return true;
}
/* This function is called by serverCron() and is used in order to perform /* This function is called by serverCron() and is used in order to perform
* operations on clients that are important to perform constantly. For instance * operations on clients that are important to perform constantly. For instance
* we use this function in order to disconnect clients after a timeout, including * we use this function in order to disconnect clients after a timeout, including
@@ -2024,6 +2041,7 @@ void clientsCron(int iel) {
if (clientsCronTrackExpansiveClients(c, curr_peak_mem_usage_slot)) goto LContinue; if (clientsCronTrackExpansiveClients(c, curr_peak_mem_usage_slot)) goto LContinue;
if (clientsCronTrackClientsMemUsage(c)) goto LContinue; if (clientsCronTrackClientsMemUsage(c)) goto LContinue;
if (closeClientOnOutputBufferLimitReached(c, 0)) continue; // Client also free'd if (closeClientOnOutputBufferLimitReached(c, 0)) continue; // Client also free'd
if (closeClientOnOverload(c)) continue;
LContinue: LContinue:
fastlock_unlock(&c->lock); fastlock_unlock(&c->lock);
} }
@@ -2074,7 +2092,7 @@ void databasesCron(bool fMainThread) {
/* Perform hash tables rehashing if needed, but only if there are no /* Perform hash tables rehashing if needed, but only if there are no
* other processes saving the DB on disk. Otherwise rehashing is bad * other processes saving the DB on disk. Otherwise rehashing is bad
* as will cause a lot of copy-on-write of memory pages. */ * as will cause a lot of copy-on-write of memory pages. */
if (!hasActiveChildProcess() || g_pserver->FRdbSaveInProgress()) { if (!hasActiveChildProcess()) {
/* We use global counters so if we stop the computation at a given /* We use global counters so if we stop the computation at a given
* DB we'll be able to start from the successive in the next * DB we'll be able to start from the successive in the next
* cron loop iteration. */ * cron loop iteration. */
@@ -2119,7 +2137,7 @@ void databasesCron(bool fMainThread) {
::dict *dict = g_pserver->db[rehash_db]->dictUnsafeKeyOnly(); ::dict *dict = g_pserver->db[rehash_db]->dictUnsafeKeyOnly();
/* Are we async rehashing? And if so is it time to re-calibrate? */ /* Are we async rehashing? And if so is it time to re-calibrate? */
/* The recalibration limit is a prime number to ensure balancing across threads */ /* The recalibration limit is a prime number to ensure balancing across threads */
if (rehashes_per_ms > 0 && async_rehashes < 131 && !cserver.active_defrag_enabled && cserver.cthreads > 1 && dictSize(dict) > 2048 && dictIsRehashing(dict) && !g_pserver->loading) { if (g_pserver->enable_async_rehash && rehashes_per_ms > 0 && async_rehashes < 131 && !cserver.active_defrag_enabled && cserver.cthreads > 1 && dictSize(dict) > 2048 && dictIsRehashing(dict) && !g_pserver->loading && aeLockContention() > 1) {
serverTL->rehashCtl = dictRehashAsyncStart(dict, rehashes_per_ms * ((1000 / g_pserver->hz) / 10)); // Estimate 10% CPU time spent in lock contention serverTL->rehashCtl = dictRehashAsyncStart(dict, rehashes_per_ms * ((1000 / g_pserver->hz) / 10)); // Estimate 10% CPU time spent in lock contention
if (serverTL->rehashCtl) if (serverTL->rehashCtl)
++async_rehashes; ++async_rehashes;
@@ -2215,6 +2233,7 @@ void checkChildrenDone(void) {
g_pserver->rdbThreadVars.fRdbThreadCancel = false; g_pserver->rdbThreadVars.fRdbThreadCancel = false;
g_pserver->rdbThreadVars.fDone = false; g_pserver->rdbThreadVars.fDone = false;
if (exitcode == 0) receiveChildInfo(); if (exitcode == 0) receiveChildInfo();
closeChildInfoPipe();
} }
} }
else if ((pid = waitpid(-1, &statloc, WNOHANG)) != 0) { else if ((pid = waitpid(-1, &statloc, WNOHANG)) != 0) {
@@ -2296,6 +2315,10 @@ void cronUpdateMemoryStats() {
g_pserver->cron_malloc_stats.allocator_active = g_pserver->cron_malloc_stats.allocator_resident; g_pserver->cron_malloc_stats.allocator_active = g_pserver->cron_malloc_stats.allocator_resident;
if (!g_pserver->cron_malloc_stats.allocator_allocated) if (!g_pserver->cron_malloc_stats.allocator_allocated)
g_pserver->cron_malloc_stats.allocator_allocated = g_pserver->cron_malloc_stats.zmalloc_used; g_pserver->cron_malloc_stats.allocator_allocated = g_pserver->cron_malloc_stats.zmalloc_used;
if (g_pserver->force_eviction_percent) {
g_pserver->cron_malloc_stats.sys_available = getMemAvailable();
}
} }
} }
@@ -2468,14 +2491,14 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Start a scheduled AOF rewrite if this was requested by the user while /* Start a scheduled AOF rewrite if this was requested by the user while
* a BGSAVE was in progress. */ * a BGSAVE was in progress. */
if (!hasActiveChildProcess() && if (!hasActiveChildProcessOrBGSave() &&
g_pserver->aof_rewrite_scheduled) g_pserver->aof_rewrite_scheduled)
{ {
rewriteAppendOnlyFileBackground(); rewriteAppendOnlyFileBackground();
} }
/* Check if a background saving or AOF rewrite in progress terminated. */ /* Check if a background saving or AOF rewrite in progress terminated. */
if (hasActiveChildProcess() || ldbPendingChildren()) if (hasActiveChildProcessOrBGSave() || ldbPendingChildren())
{ {
run_with_period(1000) receiveChildInfo(); run_with_period(1000) receiveChildInfo();
checkChildrenDone(); checkChildrenDone();
@@ -2517,7 +2540,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Trigger an AOF rewrite if needed. */ /* Trigger an AOF rewrite if needed. */
if (g_pserver->aof_state == AOF_ON && if (g_pserver->aof_state == AOF_ON &&
!hasActiveChildProcess() && !hasActiveChildProcessOrBGSave() &&
g_pserver->aof_rewrite_perc && g_pserver->aof_rewrite_perc &&
g_pserver->aof_current_size > g_pserver->aof_rewrite_min_size) g_pserver->aof_current_size > g_pserver->aof_rewrite_min_size)
{ {
@@ -2576,6 +2599,26 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
migrateCloseTimedoutSockets(); migrateCloseTimedoutSockets();
} }
/* Check for CPU Overload */
run_with_period(10'000) {
g_pserver->is_overloaded = false;
g_pserver->overload_closed_clients = 0;
static clock_t last = 0;
if (g_pserver->overload_protect_threshold > 0) {
clock_t cur = clock();
double perc = static_cast<double>(cur - last) / (CLOCKS_PER_SEC*10);
perc /= cserver.cthreads;
perc *= 100.0;
serverLog(LL_WARNING, "CPU Used: %.2f", perc);
if (perc > g_pserver->overload_protect_threshold) {
serverLog(LL_WARNING, "\tWARNING: CPU overload detected.");
g_pserver->is_overloaded = true;
}
last = cur;
}
}
/* Tune the fastlock to CPU load */
run_with_period(30000) { run_with_period(30000) {
/* Tune the fastlock to CPU load */ /* Tune the fastlock to CPU load */
fastlock_auto_adjust_waits(); fastlock_auto_adjust_waits();
@@ -2601,7 +2644,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
* Note: this code must be after the replicationCron() call above so * Note: this code must be after the replicationCron() call above so
* make sure when refactoring this file to keep this order. This is useful * make sure when refactoring this file to keep this order. This is useful
* because we want to give priority to RDB savings for replication. */ * because we want to give priority to RDB savings for replication. */
if (!hasActiveChildProcess() && if (!hasActiveChildProcessOrBGSave() &&
g_pserver->rdb_bgsave_scheduled && g_pserver->rdb_bgsave_scheduled &&
(g_pserver->unixtime-g_pserver->lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || (g_pserver->unixtime-g_pserver->lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
g_pserver->lastbgsave_status == C_OK)) g_pserver->lastbgsave_status == C_OK))
@@ -2815,9 +2858,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
runAndPropogateToReplicas(processClients); runAndPropogateToReplicas(processClients);
/* Handle precise timeouts of blocked clients. */
handleBlockedClientsTimeout();
/* Just call a subset of vital functions in case we are re-entering /* Just call a subset of vital functions in case we are re-entering
* the event loop from processEventsWhileBlocked(). Note that in this * the event loop from processEventsWhileBlocked(). Note that in this
* case we keep track of the number of events we are processing, since * case we keep track of the number of events we are processing, since
@@ -3879,9 +3919,25 @@ void initServer(void) {
g_pserver->db = (redisDb**)zmalloc(sizeof(redisDb*)*cserver.dbnum, MALLOC_LOCAL); g_pserver->db = (redisDb**)zmalloc(sizeof(redisDb*)*cserver.dbnum, MALLOC_LOCAL);
/* Create the Redis databases, and initialize other internal state. */ /* Create the Redis databases, and initialize other internal state. */
for (int j = 0; j < cserver.dbnum; j++) { if (g_pserver->m_pstorageFactory == nullptr) {
g_pserver->db[j] = new (MALLOC_LOCAL) redisDb(); for (int j = 0; j < cserver.dbnum; j++) {
g_pserver->db[j]->initialize(j); g_pserver->db[j] = new (MALLOC_LOCAL) redisDb();
g_pserver->db[j]->initialize(j);
}
} else {
// Read FLASH metadata and load the appropriate storage dbid into each databse index, as each DB index can have different storage dbid mapped due to the swapdb command.
g_pserver->metadataDb = g_pserver->m_pstorageFactory->createMetadataDb();
for (int idb = 0; idb < cserver.dbnum; ++idb)
{
int storage_dbid = idb;
std::string dbid_key = "db-" + std::to_string(idb);
g_pserver->metadataDb->retrieve(dbid_key.c_str(), dbid_key.length(), [&](const char *, size_t, const void *data, size_t){
storage_dbid = *(int*)data;
});
g_pserver->db[idb] = new (MALLOC_LOCAL) redisDb();
g_pserver->db[idb]->initialize(idb, storage_dbid);
}
} }
for (int i = 0; i < MAX_EVENT_LOOPS; ++i) for (int i = 0; i < MAX_EVENT_LOOPS; ++i)
@@ -3943,8 +3999,6 @@ void initServer(void) {
g_pserver->pubsub_channels = dictCreate(&keylistDictType,NULL); g_pserver->pubsub_channels = dictCreate(&keylistDictType,NULL);
g_pserver->pubsub_patterns = dictCreate(&keylistDictType,NULL); g_pserver->pubsub_patterns = dictCreate(&keylistDictType,NULL);
g_pserver->cronloops = 0; g_pserver->cronloops = 0;
g_pserver->propagate_in_transaction = 0;
g_pserver->client_pause_in_transaction = 0;
g_pserver->child_pid = -1; g_pserver->child_pid = -1;
g_pserver->child_type = CHILD_TYPE_NONE; g_pserver->child_type = CHILD_TYPE_NONE;
g_pserver->rdbThreadVars.fRdbThreadCancel = false; g_pserver->rdbThreadVars.fRdbThreadCancel = false;
@@ -3984,6 +4038,8 @@ void initServer(void) {
g_pserver->cron_malloc_stats.allocator_allocated = 0; g_pserver->cron_malloc_stats.allocator_allocated = 0;
g_pserver->cron_malloc_stats.allocator_active = 0; g_pserver->cron_malloc_stats.allocator_active = 0;
g_pserver->cron_malloc_stats.allocator_resident = 0; g_pserver->cron_malloc_stats.allocator_resident = 0;
g_pserver->cron_malloc_stats.sys_available = 0;
g_pserver->cron_malloc_stats.sys_total = g_pserver->force_eviction_percent ? getMemTotal() : 0;
g_pserver->lastbgsave_status = C_OK; g_pserver->lastbgsave_status = C_OK;
g_pserver->aof_last_write_status = C_OK; g_pserver->aof_last_write_status = C_OK;
g_pserver->aof_last_write_errno = 0; g_pserver->aof_last_write_errno = 0;
@@ -3991,6 +4047,7 @@ void initServer(void) {
g_pserver->mvcc_tstamp = 0; g_pserver->mvcc_tstamp = 0;
/* Create the timer callback, this is our way to process many background /* Create the timer callback, this is our way to process many background
* operations incrementally, like clients timeout, eviction of unaccessed * operations incrementally, like clients timeout, eviction of unaccessed
* expired keys and so forth. */ * expired keys and so forth. */
@@ -4031,7 +4088,6 @@ void initServer(void) {
latencyMonitorInit(); latencyMonitorInit();
if (g_pserver->m_pstorageFactory) { if (g_pserver->m_pstorageFactory) {
g_pserver->metadataDb = g_pserver->m_pstorageFactory->createMetadataDb();
if (g_pserver->metadataDb) { if (g_pserver->metadataDb) {
g_pserver->metadataDb->retrieve("repl-id", 7, [&](const char *, size_t, const void *data, size_t cb){ g_pserver->metadataDb->retrieve("repl-id", 7, [&](const char *, size_t, const void *data, size_t cb){
if (cb == sizeof(g_pserver->replid)) { if (cb == sizeof(g_pserver->replid)) {
@@ -4067,12 +4123,6 @@ void initServer(void) {
} }
} }
/* We have to initialize storage providers after the cluster has been initialized */
for (int idb = 0; idb < cserver.dbnum; ++idb)
{
g_pserver->db[idb]->storageProviderInitialize();
}
saveMasterStatusToStorage(false); // eliminate the repl-offset field saveMasterStatusToStorage(false); // eliminate the repl-offset field
/* Initialize ACL default password if it exists */ /* Initialize ACL default password if it exists */
@@ -4085,6 +4135,15 @@ void initServer(void) {
* Thread Local Storage initialization collides with dlopen call. * Thread Local Storage initialization collides with dlopen call.
* see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */ * see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */
void InitServerLast() { void InitServerLast() {
/* We have to initialize storage providers after the cluster has been initialized */
moduleFireServerEvent(REDISMODULE_EVENT_LOADING, REDISMODULE_SUBEVENT_LOADING_FLASH_START, NULL);
for (int idb = 0; idb < cserver.dbnum; ++idb)
{
g_pserver->db[idb]->storageProviderInitialize();
}
moduleFireServerEvent(REDISMODULE_EVENT_LOADING, REDISMODULE_SUBEVENT_LOADING_ENDED, NULL);
bioInit(); bioInit();
set_jemalloc_bg_thread(cserver.jemalloc_bg_thread); set_jemalloc_bg_thread(cserver.jemalloc_bg_thread);
g_pserver->initial_memory_usage = zmalloc_used_memory(); g_pserver->initial_memory_usage = zmalloc_used_memory();
@@ -4315,12 +4374,12 @@ void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
* This way we'll deliver the MULTI/..../EXEC block as a whole and * 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 * both the AOF and the replication link will have the same consistency
* and atomicity guarantees. */ * and atomicity guarantees. */
if (serverTL->in_exec && !g_pserver->propagate_in_transaction) if (serverTL->in_exec && !serverTL->propagate_in_transaction)
execCommandPropagateMulti(dbid); execCommandPropagateMulti(dbid);
/* This needs to be unreachable since the dataset should be fixed during /* This needs to be unreachable since the dataset should be fixed during
* client pause, otherwise data may be lossed during a failover. */ * client pause, otherwise data may be lossed during a failover. */
serverAssert(!(areClientsPaused() && !g_pserver->client_pause_in_transaction)); serverAssert(!(areClientsPaused() && !serverTL->client_pause_in_transaction));
if (g_pserver->aof_state != AOF_OFF && flags & PROPAGATE_AOF) if (g_pserver->aof_state != AOF_OFF && flags & PROPAGATE_AOF)
feedAppendOnlyFile(cmd,dbid,argv,argc); feedAppendOnlyFile(cmd,dbid,argv,argc);
@@ -4642,8 +4701,8 @@ void call(client *c, int flags) {
/* Client pause takes effect after a transaction has finished. This needs /* Client pause takes effect after a transaction has finished. This needs
* to be located after everything is propagated. */ * to be located after everything is propagated. */
if (!serverTL->in_exec && g_pserver->client_pause_in_transaction) { if (!serverTL->in_exec && serverTL->client_pause_in_transaction) {
g_pserver->client_pause_in_transaction = 0; serverTL->client_pause_in_transaction = 0;
} }
/* If the client has keys tracking enabled for client side caching, /* If the client has keys tracking enabled for client side caching,
@@ -4734,8 +4793,9 @@ int processCommand(client *c, int callFlags) {
/* Both EXEC and EVAL call call() directly so there should be /* Both EXEC and EVAL call call() directly so there should be
* no way in_exec or in_eval or propagate_in_transaction is 1. * no way in_exec or in_eval or propagate_in_transaction is 1.
* That is unless lua_timedout, in which case client may run * That is unless lua_timedout, in which case client may run
* some commands. */ * some commands. Also possible that some other thread set
serverAssert(!g_pserver->propagate_in_transaction); * propagate_in_transaction if this is an async command. */
serverAssert(!serverTL->propagate_in_transaction);
serverAssert(!serverTL->in_exec); serverAssert(!serverTL->in_exec);
serverAssert(!serverTL->in_eval); serverAssert(!serverTL->in_eval);
} }
@@ -5032,10 +5092,29 @@ int processCommand(client *c, int callFlags) {
} else { } else {
/* If the command was replication or admin related we *must* flush our buffers first. This is in case /* If the command was replication or admin related we *must* flush our buffers first. This is in case
something happens which would modify what we would send to replicas */ something happens which would modify what we would send to replicas */
if (c->cmd->flags & (CMD_MODULE | CMD_ADMIN)) if (c->cmd->flags & (CMD_MODULE | CMD_ADMIN))
flushReplBacklogToClients(); flushReplBacklogToClients();
if (c->flags & CLIENT_AUDIT_LOGGING){
getKeysResult result = GETKEYS_RESULT_INIT;
int numkeys = getKeysFromCommand(c->cmd, c->argv, c->argc, &result);
int *keyindex = result.keys;
sds str = sdsempty();
for (int j = 0; j < numkeys; j++) {
str = sdscatsds(str, (sds)ptrFromObj(c->argv[keyindex[j]]));
str = sdscat(str, " ");
}
if (numkeys > 0)
{
serverLog(LL_NOTICE, "Audit Log: %s, cmd %s, keys: %s", c->fprint, c->cmd->name, str);
} else {
serverLog(LL_NOTICE, "Audit Log: %s, cmd %s", c->fprint, c->cmd->name);
}
sdsfree(str);
}
call(c,callFlags); call(c,callFlags);
c->woff = g_pserver->master_repl_offset; c->woff = g_pserver->master_repl_offset;
@@ -5167,7 +5246,7 @@ int prepareForShutdown(int flags) {
* to unlink file actully) in background thread. * to unlink file actully) in background thread.
* The temp rdb file fd may won't be closed when redis exits quickly, * The temp rdb file fd may won't be closed when redis exits quickly,
* but OS will close this fd when process exits. */ * but OS will close this fd when process exits. */
rdbRemoveTempFile(g_pserver->child_pid, 0); rdbRemoveTempFile(g_pserver->rdbThreadVars.tmpfileNum, 0);
} }
/* Kill module child if there is one. */ /* Kill module child if there is one. */
@@ -5467,30 +5546,30 @@ NULL
/* Convert an amount of bytes into a human readable string in the form /* Convert an amount of bytes into a human readable string in the form
* of 100B, 2G, 100M, 4K, and so forth. */ * of 100B, 2G, 100M, 4K, and so forth. */
void bytesToHuman(char *s, unsigned long long n) { void bytesToHuman(char *s, unsigned long long n, size_t bufsize) {
double d; double d;
if (n < 1024) { if (n < 1024) {
/* Bytes */ /* Bytes */
snprintf(s,256,"%lluB",n); snprintf(s,bufsize,"%lluB",n);
} else if (n < (1024*1024)) { } else if (n < (1024*1024)) {
d = (double)n/(1024); d = (double)n/(1024);
snprintf(s,256,"%.2fK",d); snprintf(s,bufsize,"%.2fK",d);
} else if (n < (1024LL*1024*1024)) { } else if (n < (1024LL*1024*1024)) {
d = (double)n/(1024*1024); d = (double)n/(1024*1024);
snprintf(s,256,"%.2fM",d); snprintf(s,bufsize,"%.2fM",d);
} else if (n < (1024LL*1024*1024*1024)) { } else if (n < (1024LL*1024*1024*1024)) {
d = (double)n/(1024LL*1024*1024); d = (double)n/(1024LL*1024*1024);
snprintf(s,256,"%.2fG",d); snprintf(s,bufsize,"%.2fG",d);
} else if (n < (1024LL*1024*1024*1024*1024)) { } else if (n < (1024LL*1024*1024*1024*1024)) {
d = (double)n/(1024LL*1024*1024*1024); d = (double)n/(1024LL*1024*1024*1024);
snprintf(s,256,"%.2fT",d); snprintf(s,bufsize,"%.2fT",d);
} else if (n < (1024LL*1024*1024*1024*1024*1024)) { } else if (n < (1024LL*1024*1024*1024*1024*1024)) {
d = (double)n/(1024LL*1024*1024*1024*1024); d = (double)n/(1024LL*1024*1024*1024*1024);
snprintf(s,256,"%.2fP",d); snprintf(s,bufsize,"%.2fP",d);
} else { } else {
/* Let's hope we never need this */ /* Let's hope we never need this */
snprintf(s,256,"%lluB",n); snprintf(s,bufsize,"%lluB",n);
} }
} }
@@ -5583,7 +5662,9 @@ sds genRedisInfoString(const char *section) {
"configured_hz:%i\r\n" "configured_hz:%i\r\n"
"lru_clock:%u\r\n" "lru_clock:%u\r\n"
"executable:%s\r\n" "executable:%s\r\n"
"config_file:%s\r\n", "config_file:%s\r\n"
"availability_zone:%s\r\n"
"features:%s\r\n",
KEYDB_SET_VERSION, KEYDB_SET_VERSION,
redisGitSHA1(), redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0, strtol(redisGitDirty(),NULL,10) > 0,
@@ -5609,7 +5690,9 @@ sds genRedisInfoString(const char *section) {
g_pserver->config_hz, g_pserver->config_hz,
lruclock, lruclock,
cserver.executable ? cserver.executable : "", cserver.executable ? cserver.executable : "",
cserver.configfile ? cserver.configfile : ""); cserver.configfile ? cserver.configfile : "",
g_pserver->sdsAvailabilityZone,
"cluster_mget");
} }
/* Clients */ /* Clients */
@@ -5658,6 +5741,7 @@ sds genRedisInfoString(const char *section) {
const char *evict_policy = evictPolicyToString(); const char *evict_policy = evictPolicyToString();
long long memory_lua = g_pserver->lua ? (long long)lua_gc(g_pserver->lua,LUA_GCCOUNT,0)*1024 : 0; long long memory_lua = g_pserver->lua ? (long long)lua_gc(g_pserver->lua,LUA_GCCOUNT,0)*1024 : 0;
struct redisMemOverhead *mh = getMemoryOverheadData(); struct redisMemOverhead *mh = getMemoryOverheadData();
char available_system_mem[64] = "unavailable";
/* Peak memory is updated from time to time by serverCron() so it /* Peak memory is updated from time to time by serverCron() so it
* may happen that the instantaneous value is slightly bigger than * may happen that the instantaneous value is slightly bigger than
@@ -5666,13 +5750,17 @@ sds genRedisInfoString(const char *section) {
if (zmalloc_used > g_pserver->stat_peak_memory) if (zmalloc_used > g_pserver->stat_peak_memory)
g_pserver->stat_peak_memory = zmalloc_used; g_pserver->stat_peak_memory = zmalloc_used;
bytesToHuman(hmem,zmalloc_used); if (g_pserver->cron_malloc_stats.sys_available) {
bytesToHuman(peak_hmem,g_pserver->stat_peak_memory); snprintf(available_system_mem, 64, "%lu", g_pserver->cron_malloc_stats.sys_available);
bytesToHuman(total_system_hmem,total_system_mem); }
bytesToHuman(used_memory_lua_hmem,memory_lua);
bytesToHuman(used_memory_scripts_hmem,mh->lua_caches); bytesToHuman(hmem,zmalloc_used,sizeof(hmem));
bytesToHuman(used_memory_rss_hmem,g_pserver->cron_malloc_stats.process_rss); bytesToHuman(peak_hmem,g_pserver->stat_peak_memory,sizeof(peak_hmem));
bytesToHuman(maxmemory_hmem,g_pserver->maxmemory); bytesToHuman(total_system_hmem,total_system_mem,sizeof(total_system_hmem));
bytesToHuman(used_memory_lua_hmem,memory_lua,sizeof(used_memory_lua_hmem));
bytesToHuman(used_memory_scripts_hmem,mh->lua_caches,sizeof(used_memory_scripts_hmem));
bytesToHuman(used_memory_rss_hmem,g_pserver->cron_malloc_stats.process_rss,sizeof(used_memory_rss_hmem));
bytesToHuman(maxmemory_hmem,g_pserver->maxmemory,sizeof(maxmemory_hmem));
if (sections++) info = sdscat(info,"\r\n"); if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info, info = sdscatprintf(info,
@@ -5718,7 +5806,8 @@ sds genRedisInfoString(const char *section) {
"active_defrag_running:%d\r\n" "active_defrag_running:%d\r\n"
"lazyfree_pending_objects:%zu\r\n" "lazyfree_pending_objects:%zu\r\n"
"lazyfreed_objects:%zu\r\n" "lazyfreed_objects:%zu\r\n"
"storage_provider:%s\r\n", "storage_provider:%s\r\n"
"available_system_memory:%s\r\n",
zmalloc_used, zmalloc_used,
hmem, hmem,
g_pserver->cron_malloc_stats.process_rss, g_pserver->cron_malloc_stats.process_rss,
@@ -5763,18 +5852,10 @@ sds genRedisInfoString(const char *section) {
g_pserver->active_defrag_running, g_pserver->active_defrag_running,
lazyfreeGetPendingObjectsCount(), lazyfreeGetPendingObjectsCount(),
lazyfreeGetFreedObjectsCount(), lazyfreeGetFreedObjectsCount(),
g_pserver->m_pstorageFactory ? g_pserver->m_pstorageFactory->name() : "none" g_pserver->m_pstorageFactory ? g_pserver->m_pstorageFactory->name() : "none",
available_system_mem
); );
freeMemoryOverheadData(mh); freeMemoryOverheadData(mh);
if (g_pserver->m_pstorageFactory)
{
info = sdscatprintf(info,
"%s_memory:%zu\r\n",
g_pserver->m_pstorageFactory->name(),
g_pserver->m_pstorageFactory->totalDiskspaceUsed()
);
}
} }
/* Persistence */ /* Persistence */
@@ -5898,6 +5979,10 @@ sds genRedisInfoString(const char *section) {
(intmax_t)eta (intmax_t)eta
); );
} }
if (g_pserver->m_pstorageFactory)
{
info = sdscat(info, g_pserver->m_pstorageFactory->getInfo().get());
}
} }
/* Stats */ /* Stats */
@@ -6559,7 +6644,8 @@ void usage(void) {
void redisAsciiArt(void) { void redisAsciiArt(void) {
#include "asciilogo.h" #include "asciilogo.h"
char *buf = (char*)zmalloc(1024*16, MALLOC_LOCAL); size_t bufsize = 1024*16;
char *buf = (char*)zmalloc(bufsize, MALLOC_LOCAL);
const char *mode; const char *mode;
if (g_pserver->cluster_enabled) mode = "cluster"; if (g_pserver->cluster_enabled) mode = "cluster";
@@ -6581,7 +6667,7 @@ void redisAsciiArt(void) {
); );
} else { } else {
sds motd = fetchMOTD(true, cserver.enable_motd); sds motd = fetchMOTD(true, cserver.enable_motd);
snprintf(buf,1024*16,ascii_logo, snprintf(buf,bufsize,ascii_logo,
KEYDB_REAL_VERSION, KEYDB_REAL_VERSION,
redisGitSHA1(), redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0, strtol(redisGitDirty(),NULL,10) > 0,
@@ -6884,6 +6970,7 @@ int redisFork(int purpose) {
latencyAddSampleIfNeeded("fork-lock",(ustime()-startWriteLock)/1000); latencyAddSampleIfNeeded("fork-lock",(ustime()-startWriteLock)/1000);
if ((childpid = fork()) == 0) { if ((childpid = fork()) == 0) {
/* Child */ /* Child */
aeForkLockInChild();
aeReleaseForkLock(); aeReleaseForkLock();
g_pserver->in_fork_child = purpose; g_pserver->in_fork_child = purpose;
setOOMScoreAdj(CONFIG_OOM_BGCHILD); setOOMScoreAdj(CONFIG_OOM_BGCHILD);
@@ -7326,7 +7413,7 @@ static void validateConfiguration()
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
g_pserver->repl_backlog_config_size = g_pserver->repl_backlog_size; // this is normally set in the update logic, but not on initial config g_pserver->repl_backlog_size = g_pserver->repl_backlog_config_size; // this is normally set in the update logic, but not on initial config
} }
int iAmMaster(void) { int iAmMaster(void) {
@@ -7566,6 +7653,29 @@ int main(int argc, char **argv) {
validateConfiguration(); validateConfiguration();
if (!g_pserver->sentinel_mode) {
#ifdef __linux__
linuxMemoryWarnings();
#if defined (__arm64__)
int ret;
if ((ret = linuxMadvFreeForkBugCheck())) {
if (ret == 1)
serverLog(LL_WARNING,"WARNING Your kernel has a bug that could lead to data corruption during background save. "
"Please upgrade to the latest stable kernel.");
else
serverLog(LL_WARNING, "Failed to test the kernel for a bug that could lead to data corruption during background save. "
"Your system could be affected, please report this error.");
if (!checkIgnoreWarning("ARM64-COW-BUG")) {
serverLog(LL_WARNING,"KeyDB will now exit to prevent data corruption. "
"Note that it is possible to suppress this warning by setting the following config: ignore-warnings ARM64-COW-BUG");
exit(1);
}
}
#endif /* __arm64__ */
#endif /* __linux__ */
}
const char *err; const char *err;
if (!initializeStorageProvider(&err)) if (!initializeStorageProvider(&err))
{ {
@@ -7591,25 +7701,6 @@ int main(int argc, char **argv) {
if (!g_pserver->sentinel_mode) { if (!g_pserver->sentinel_mode) {
/* Things not needed when running in Sentinel mode. */ /* Things not needed when running in Sentinel mode. */
serverLog(LL_WARNING,"Server initialized"); serverLog(LL_WARNING,"Server initialized");
#ifdef __linux__
linuxMemoryWarnings();
#if defined (__arm64__)
int ret;
if ((ret = linuxMadvFreeForkBugCheck())) {
if (ret == 1)
serverLog(LL_WARNING,"WARNING Your kernel has a bug that could lead to data corruption during background save. "
"Please upgrade to the latest stable kernel.");
else
serverLog(LL_WARNING, "Failed to test the kernel for a bug that could lead to data corruption during background save. "
"Your system could be affected, please report this error.");
if (!checkIgnoreWarning("ARM64-COW-BUG")) {
serverLog(LL_WARNING,"KeyDB will now exit to prevent data corruption. "
"Note that it is possible to suppress this warning by setting the following config: ignore-warnings ARM64-COW-BUG");
exit(1);
}
}
#endif /* __arm64__ */
#endif /* __linux__ */
moduleInitModulesSystemLast(); moduleInitModulesSystemLast();
moduleLoadFromQueue(); moduleLoadFromQueue();
ACLLoadUsersAtStartup(); ACLLoadUsersAtStartup();
+80 -40
View File
@@ -122,6 +122,9 @@ typedef long long ustime_t; /* microsecond time type. */
#define LOADING_BOOT 1 #define LOADING_BOOT 1
#define LOADING_REPLICATION 2 #define LOADING_REPLICATION 2
#define OVERLOAD_PROTECT_PERIOD_MS 10'000 // 10 seconds
#define MAX_CLIENTS_SHED_PER_PERIOD (OVERLOAD_PROTECT_PERIOD_MS / 10) // Restrict to one client per 10ms
extern int g_fTestMode; extern int g_fTestMode;
extern struct redisServer *g_pserver; extern struct redisServer *g_pserver;
@@ -542,6 +545,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define CLIENT_REPL_RDBONLY (1ULL<<42) /* This client is a replica that only wants #define CLIENT_REPL_RDBONLY (1ULL<<42) /* This client is a replica that only wants
RDB without replication buffer. */ RDB without replication buffer. */
#define CLIENT_FORCE_REPLY (1ULL<<44) /* Should addReply be forced to write the text? */ #define CLIENT_FORCE_REPLY (1ULL<<44) /* Should addReply be forced to write the text? */
#define CLIENT_AUDIT_LOGGING (1ULL<<45) /* Client commands required audit logging */
/* Client block type (btype field in client structure) /* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */ * if CLIENT_BLOCKED flag is set. */
@@ -616,7 +620,7 @@ typedef enum {
#define SLAVE_CAPA_EOF (1<<0) /* Can parse the RDB EOF streaming format. */ #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_PSYNC2 (1<<1) /* Supports PSYNC2 protocol. */
#define SLAVE_CAPA_ACTIVE_EXPIRE (1<<2) /* Will the slave perform its own expirations? (Don't send delete) */ #define SLAVE_CAPA_ACTIVE_EXPIRE (1<<2) /* Will the slave perform its own expirations? (Don't send delete) */
#define SLAVE_CAPA_ROCKSDB_SNAPSHOT (1<<3) #define SLAVE_CAPA_KEYDB_FASTSYNC (1<<3)
/* Synchronous read timeout - replica side */ /* Synchronous read timeout - replica side */
#define CONFIG_REPL_SYNCIO_TIMEOUT 5 #define CONFIG_REPL_SYNCIO_TIMEOUT 5
@@ -962,6 +966,7 @@ struct redisObjectExtended {
typedef struct redisObject { typedef struct redisObject {
friend redisObject *createEmbeddedStringObject(const char *ptr, size_t len); friend redisObject *createEmbeddedStringObject(const char *ptr, size_t len);
friend redisObject *createObject(int type, void *ptr);
protected: protected:
redisObject() {} redisObject() {}
@@ -974,6 +979,7 @@ public:
private: private:
mutable std::atomic<unsigned> refcount {0}; mutable std::atomic<unsigned> refcount {0};
public: public:
expireEntry expire;
void *m_ptr; void *m_ptr;
inline bool FExpires() const { return refcount.load(std::memory_order_relaxed) >> 31; } inline bool FExpires() const { return refcount.load(std::memory_order_relaxed) >> 31; }
@@ -984,7 +990,7 @@ public:
void addref() const { refcount.fetch_add(1, std::memory_order_relaxed); } void addref() const { refcount.fetch_add(1, std::memory_order_relaxed); }
unsigned release() const { return refcount.fetch_sub(1, std::memory_order_seq_cst) & ~(1U << 31); } unsigned release() const { return refcount.fetch_sub(1, std::memory_order_seq_cst) & ~(1U << 31); }
} robj; } robj;
static_assert(sizeof(redisObject) <= 16, "object size is critical, don't increase"); static_assert(sizeof(redisObject) <= 24, "object size is critical, don't increase");
class redisObjectStack : public redisObjectExtended, public redisObject class redisObjectStack : public redisObjectExtended, public redisObject
{ {
@@ -1140,16 +1146,20 @@ public:
dict_iter random(); dict_iter random();
const expireEntry &random_expire() const expireEntry *random_expire(sds *key)
{ {
return m_setexpire->random_value(); auto itr = random();
if (itr->FExpires()) {
*key = itr.key();
return &itr->expire;
}
return nullptr;
} }
dict_iter end() { return dict_iter(nullptr, nullptr); } dict_iter end() { return dict_iter(nullptr, nullptr); }
dict_const_iter end() const { return dict_const_iter(nullptr); } dict_const_iter end() const { return dict_const_iter(nullptr); }
void getStats(char *buf, size_t bufsize) { dictGetStats(buf, bufsize, m_pdict); } void getStats(char *buf, size_t bufsize) { dictGetStats(buf, bufsize, m_pdict); }
void getExpireStats(char *buf, size_t bufsize) { m_setexpire->getstats(buf, bufsize); }
bool insert(char *k, robj *o, bool fAssumeNew = false, dict_iter *existing = nullptr); bool insert(char *k, robj *o, bool fAssumeNew = false, dict_iter *existing = nullptr);
void tryResize(); void tryResize();
@@ -1157,16 +1167,15 @@ public:
void updateValue(dict_iter itr, robj *val); void updateValue(dict_iter itr, robj *val);
bool syncDelete(robj *key); bool syncDelete(robj *key);
bool asyncDelete(robj *key); bool asyncDelete(robj *key);
size_t expireSize() const { return m_setexpire->size(); } size_t expireSize() const { return m_numexpires; }
int removeExpire(robj *key, dict_iter itr); int removeExpire(robj *key, dict_iter itr);
int removeSubkeyExpire(robj *key, robj *subkey); int removeSubkeyExpire(robj *key, robj *subkey);
void resortExpire(expireEntry &e);
void clear(void(callback)(void*)); void clear(void(callback)(void*));
void emptyDbAsync(); void emptyDbAsync();
// Note: If you do not need the obj then use the objless iterator version. It's faster // Note: If you do not need the obj then use the objless iterator version. It's faster
bool iterate(std::function<bool(const char*, robj*)> fn); bool iterate(std::function<bool(const char*, robj*)> fn);
void setExpire(robj *key, robj *subkey, long long when); void setExpire(robj *key, robj *subkey, long long when);
void setExpire(expireEntry &&e); void setExpire(const char *key, expireEntry &&e);
void initialize(); void initialize();
void prepOverwriteForSnapshot(char *key); void prepOverwriteForSnapshot(char *key);
@@ -1190,9 +1199,6 @@ public:
// objects stored elsewhere // objects stored elsewhere
dict *dictUnsafeKeyOnly() { return m_pdict; } dict *dictUnsafeKeyOnly() { return m_pdict; }
expireset *setexpireUnsafe() { return m_setexpire; }
const expireset *setexpire() const { return m_setexpire; }
const redisDbPersistentDataSnapshot *createSnapshot(uint64_t mvccCheckpoint, bool fOptional); const redisDbPersistentDataSnapshot *createSnapshot(uint64_t mvccCheckpoint, bool fOptional);
void endSnapshot(const redisDbPersistentDataSnapshot *psnapshot); void endSnapshot(const redisDbPersistentDataSnapshot *psnapshot);
void endSnapshotAsync(const redisDbPersistentDataSnapshot *psnapshot); void endSnapshotAsync(const redisDbPersistentDataSnapshot *psnapshot);
@@ -1204,15 +1210,18 @@ public:
void disableKeyCache(); void disableKeyCache();
bool keycacheIsEnabled(); bool keycacheIsEnabled();
bool prefetchKeysAsync(client *c, struct parsed_command &command, bool fExecOK); void prefetchKeysAsync(client *c, struct parsed_command &command);
bool FSnapshot() const { return m_spdbSnapshotHOLDER != nullptr; } bool FSnapshot() const { return m_spdbSnapshotHOLDER != nullptr; }
std::unique_ptr<const StorageCache> CloneStorageCache() { return std::unique_ptr<const StorageCache>(m_spstorage->clone()); } std::unique_ptr<const StorageCache> CloneStorageCache() { return std::unique_ptr<const StorageCache>(m_spstorage->clone()); }
void bulkStorageInsert(char **rgKeys, size_t *rgcbKeys, char **rgVals, size_t *rgcbVals, size_t celem); std::shared_ptr<StorageCache> getStorageCache() { return m_spstorage; }
void bulkDirectStorageInsert(char **rgKeys, size_t *rgcbKeys, char **rgVals, size_t *rgcbVals, size_t celem);
dict_iter find_cached_threadsafe(const char *key) const; dict_iter find_cached_threadsafe(const char *key) const;
static void activeExpireCycleCore(int type);
protected: protected:
uint64_t m_mvccCheckpoint = 0; uint64_t m_mvccCheckpoint = 0;
@@ -1235,7 +1244,7 @@ private:
std::shared_ptr<StorageCache> m_spstorage = nullptr; std::shared_ptr<StorageCache> m_spstorage = nullptr;
// Expire // Expire
expireset *m_setexpire = nullptr; size_t m_numexpires = 0;
// These two pointers are the same, UNLESS the database has been cleared. // These two pointers are the same, UNLESS the database has been cleared.
// in which case m_pdbSnapshot is NULL and we continue as though we weren' // in which case m_pdbSnapshot is NULL and we continue as though we weren'
@@ -1305,7 +1314,7 @@ struct redisDb : public redisDbPersistentDataSnapshot
friend int removeExpire(redisDb *db, robj *key); friend int removeExpire(redisDb *db, robj *key);
friend void setExpire(struct client *c, redisDb *db, robj *key, robj *subkey, long long when); friend void setExpire(struct client *c, redisDb *db, robj *key, robj *subkey, long long when);
friend void setExpire(client *c, redisDb *db, robj *key, expireEntry &&e); friend void setExpire(client *c, redisDb *db, robj *key, expireEntry &&e);
friend int evictionPoolPopulate(int dbid, redisDb *db, expireset *setexpire, struct evictionPoolEntry *pool); friend int evictionPoolPopulate(int dbid, redisDb *db, bool fVolatile, struct evictionPoolEntry *pool);
friend void activeDefragCycle(void); friend void activeDefragCycle(void);
friend void activeExpireCycle(int); friend void activeExpireCycle(int);
friend void expireSlaveKeys(void); friend void expireSlaveKeys(void);
@@ -1314,11 +1323,9 @@ struct redisDb : public redisDbPersistentDataSnapshot
typedef ::dict_const_iter const_iter; typedef ::dict_const_iter const_iter;
typedef ::dict_iter iter; typedef ::dict_iter iter;
redisDb() redisDb() = default;
: expireitr(nullptr)
{}
void initialize(int id); void initialize(int id, int storage_id=-1 /* default no storage */);
void storageProviderInitialize(); void storageProviderInitialize();
void storageProviderDelete(); void storageProviderDelete();
virtual ~redisDb(); virtual ~redisDb();
@@ -1338,7 +1345,6 @@ struct redisDb : public redisDbPersistentDataSnapshot
using redisDbPersistentData::random_expire; using redisDbPersistentData::random_expire;
using redisDbPersistentData::end; using redisDbPersistentData::end;
using redisDbPersistentData::getStats; using redisDbPersistentData::getStats;
using redisDbPersistentData::getExpireStats;
using redisDbPersistentData::insert; using redisDbPersistentData::insert;
using redisDbPersistentData::tryResize; using redisDbPersistentData::tryResize;
using redisDbPersistentData::incrementallyRehash; using redisDbPersistentData::incrementallyRehash;
@@ -1356,21 +1362,19 @@ struct redisDb : public redisDbPersistentDataSnapshot
using redisDbPersistentData::processChanges; using redisDbPersistentData::processChanges;
using redisDbPersistentData::processChangesAsync; using redisDbPersistentData::processChangesAsync;
using redisDbPersistentData::commitChanges; using redisDbPersistentData::commitChanges;
using redisDbPersistentData::setexpireUnsafe;
using redisDbPersistentData::setexpire;
using redisDbPersistentData::endSnapshot; using redisDbPersistentData::endSnapshot;
using redisDbPersistentData::restoreSnapshot; using redisDbPersistentData::restoreSnapshot;
using redisDbPersistentData::removeAllCachedValues; using redisDbPersistentData::removeAllCachedValues;
using redisDbPersistentData::disableKeyCache; using redisDbPersistentData::disableKeyCache;
using redisDbPersistentData::keycacheIsEnabled; using redisDbPersistentData::keycacheIsEnabled;
using redisDbPersistentData::dictUnsafeKeyOnly; using redisDbPersistentData::dictUnsafeKeyOnly;
using redisDbPersistentData::resortExpire;
using redisDbPersistentData::prefetchKeysAsync; using redisDbPersistentData::prefetchKeysAsync;
using redisDbPersistentData::prepOverwriteForSnapshot; using redisDbPersistentData::prepOverwriteForSnapshot;
using redisDbPersistentData::FRehashing; using redisDbPersistentData::FRehashing;
using redisDbPersistentData::FTrackingChanges; using redisDbPersistentData::FTrackingChanges;
using redisDbPersistentData::CloneStorageCache; using redisDbPersistentData::CloneStorageCache;
using redisDbPersistentData::bulkStorageInsert; using redisDbPersistentData::getStorageCache;
using redisDbPersistentData::bulkDirectStorageInsert;
public: public:
const redisDbPersistentDataSnapshot *createSnapshot(uint64_t mvccCheckpoint, bool fOptional) { const redisDbPersistentDataSnapshot *createSnapshot(uint64_t mvccCheckpoint, bool fOptional) {
@@ -1380,11 +1384,12 @@ public:
return psnapshot; return psnapshot;
} }
expireset::setiter expireitr; unsigned long expires_cursor = 0;
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/ dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */ dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */ int id; /* Database ID */
int storage_id; /* Mapped storage provider DB id which is same as the redisdb id above. But, when the database is swapped, the redisdb id above might be swapped to be consistent with the database index (id <-> g_pserver->db[index]) however the storage_id remains unchanged in order to maintain correct mapping to the underlying storage provider DB. This is valid only if there is a storage provider set.*/
long long last_expire_set; /* when the last expire was set */ long long last_expire_set; /* when the last expire was set */
double avg_ttl; /* Average TTL, just for stats */ double avg_ttl; /* Average TTL, just for stats */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */ list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
@@ -1606,7 +1611,6 @@ struct client {
unsigned long long reply_bytes; /* Tot bytes of objects in reply list. */ unsigned long long reply_bytes; /* Tot bytes of objects in reply list. */
size_t sentlen; /* Amount of bytes already sent in the current size_t sentlen; /* Amount of bytes already sent in the current
buffer or object being sent. */ buffer or object being sent. */
size_t sentlenAsync; /* same as sentlen buf for async buffers (which are a different stream) */
time_t ctime; /* Client creation time. */ time_t ctime; /* Client creation time. */
long duration; /* Current command duration. Used for measuring latency of blocking/non-blocking cmds */ long duration; /* Current command duration. Used for measuring latency of blocking/non-blocking cmds */
time_t lastinteraction; /* Time of the last interaction, used for timeout */ time_t lastinteraction; /* Time of the last interaction, used for timeout */
@@ -1708,6 +1712,7 @@ struct client {
size_t argv_len_sum() const; size_t argv_len_sum() const;
bool asyncCommand(std::function<void(const redisDbPersistentDataSnapshot *, const std::vector<robj_sharedptr> &)> &&mainFn, bool asyncCommand(std::function<void(const redisDbPersistentDataSnapshot *, const std::vector<robj_sharedptr> &)> &&mainFn,
std::function<void(const redisDbPersistentDataSnapshot *)> &&postFn = nullptr); std::function<void(const redisDbPersistentDataSnapshot *)> &&postFn = nullptr);
char* fprint;
}; };
struct saveparam { struct saveparam {
@@ -1866,7 +1871,7 @@ struct redisMaster {
long long master_initial_offset; /* Master PSYNC offset. */ long long master_initial_offset; /* Master PSYNC offset. */
bool isActive = false; bool isActive = false;
bool isRocksdbSnapshotRepl = false; bool isKeydbFastsync = false;
int repl_state; /* Replication status if the instance is a replica */ 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_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_read; /* Amount of RDB read from master during sync. */
@@ -2007,6 +2012,8 @@ struct malloc_stats {
size_t allocator_allocated; size_t allocator_allocated;
size_t allocator_active; size_t allocator_active;
size_t allocator_resident; size_t allocator_resident;
size_t sys_total;
size_t sys_available;
}; };
typedef struct socketFds { typedef struct socketFds {
@@ -2197,6 +2204,9 @@ struct redisServerThreadVars {
bool modulesEnabledThisAeLoop = false; /* In this loop of aeMain, were modules enabled before bool modulesEnabledThisAeLoop = false; /* In this loop of aeMain, were modules enabled before
the thread went to sleep? */ the thread went to sleep? */
bool disable_async_commands = false; /* this is only valid for one cycle of the AE loop and is reset in afterSleep */ bool disable_async_commands = false; /* this is only valid for one cycle of the AE loop and is reset in afterSleep */
int propagate_in_transaction = 0; /* Make sure we don't propagate nested MULTI/EXEC */
int client_pause_in_transaction = 0; /* Was a client pause executed during this Exec? */
std::vector<client*> vecclientsProcess; std::vector<client*> vecclientsProcess;
dictAsyncRehashCtl *rehashCtl = nullptr; dictAsyncRehashCtl *rehashCtl = nullptr;
@@ -2235,7 +2245,6 @@ struct redisServerConst {
int maxidletime; /* Client timeout in seconds */ int maxidletime; /* Client timeout in seconds */
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */ int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
int active_expire_enabled; /* Can be disabled for testing purposes. */ int active_expire_enabled; /* Can be disabled for testing purposes. */
int active_expire_effort; /* From 1 (default) to 10, active effort. */
int active_defrag_enabled; int active_defrag_enabled;
int jemalloc_bg_thread; /* Enable jemalloc background thread */ int jemalloc_bg_thread; /* Enable jemalloc background thread */
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */ size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
@@ -2297,14 +2306,13 @@ struct redisServer {
rax *errors; /* Errors table */ rax *errors; /* Errors table */
int activerehashing; /* Incremental rehash in serverCron() */ int activerehashing; /* Incremental rehash in serverCron() */
int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */ int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */
int enable_async_rehash = 1; /* Should we use the async rehash feature? */
int cronloops; /* Number of times the cron function run */ int cronloops; /* Number of times the cron function run */
char runid[CONFIG_RUN_ID_SIZE+1]; /* ID always different at every exec. */ char runid[CONFIG_RUN_ID_SIZE+1]; /* ID always different at every exec. */
int sentinel_mode; /* True if this instance is a Sentinel. */ int sentinel_mode; /* True if this instance is a Sentinel. */
size_t initial_memory_usage; /* Bytes used after initialization. */ size_t initial_memory_usage; /* Bytes used after initialization. */
int always_show_logo; /* Show logo even for non-stdout logging. */ int always_show_logo; /* Show logo even for non-stdout logging. */
int propagate_in_transaction; /* Make sure we don't propagate nested MULTI/EXEC */
char *ignore_warnings; /* Config: warnings that should be ignored. */ char *ignore_warnings; /* Config: warnings that should be ignored. */
int client_pause_in_transaction; /* Was a client pause executed during this Exec? */
pause_type client_pause_type; /* True if clients are currently paused */ pause_type client_pause_type; /* True if clients are currently paused */
/* Modules */ /* Modules */
::dict *moduleapi; /* Exported core APIs dictionary for modules. */ ::dict *moduleapi; /* Exported core APIs dictionary for modules. */
@@ -2347,6 +2355,7 @@ struct redisServer {
unsigned int loading_process_events_interval_keys; unsigned int loading_process_events_interval_keys;
int active_expire_enabled; /* Can be disabled for testing purposes. */ int active_expire_enabled; /* Can be disabled for testing purposes. */
int active_expire_effort; /* From 1 (default) to 10, active effort. */
int replicaIsolationFactor = 1; int replicaIsolationFactor = 1;
@@ -2470,7 +2479,8 @@ struct redisServer {
time_t lastbgsave_try; /* Unix time of last attempted bgsave */ time_t lastbgsave_try; /* Unix time of last attempted bgsave */
time_t rdb_save_time_last; /* Time used by last RDB save run. */ time_t rdb_save_time_last; /* Time used by last RDB save run. */
time_t rdb_save_time_start; /* Current RDB save start time. */ time_t rdb_save_time_start; /* Current RDB save start time. */
pid_t rdb_child_pid = -1; /* Used only during fork bgsave */ mstime_t rdb_save_latency; /* Used to track end to end latency of rdb save*/
pid_t rdb_child_pid = -1; /* Used only during fork bgsave */
int rdb_bgsave_scheduled; /* BGSAVE when possible if true. */ int rdb_bgsave_scheduled; /* BGSAVE when possible if true. */
int rdb_child_type; /* Type of save by active child. */ int rdb_child_type; /* Type of save by active child. */
int lastbgsave_status; /* C_OK or C_ERR */ int lastbgsave_status; /* C_OK or C_ERR */
@@ -2561,11 +2571,13 @@ struct redisServer {
int get_ack_from_slaves; /* If true we send REPLCONF GETACK. */ int get_ack_from_slaves; /* If true we send REPLCONF GETACK. */
/* Limits */ /* Limits */
unsigned int maxclients; /* Max number of simultaneous clients */ unsigned int maxclients; /* Max number of simultaneous clients */
unsigned int maxclientsReserved; /* Reserved amount for health checks (localhost conns) */
unsigned long long maxmemory; /* Max number of memory bytes to use */ unsigned long long maxmemory; /* Max number of memory bytes to use */
unsigned long long maxstorage; /* Max number of bytes to use in a storage provider */ unsigned long long maxstorage; /* Max number of bytes to use in a storage provider */
int maxmemory_policy; /* Policy for key eviction */ int maxmemory_policy; /* Policy for key eviction */
int maxmemory_samples; /* Precision of random sampling */ int maxmemory_samples; /* Precision of random sampling */
int maxmemory_eviction_tenacity;/* Aggressiveness of eviction processing */ int maxmemory_eviction_tenacity;/* Aggressiveness of eviction processing */
int force_eviction_percent; /* Force eviction when this percent of system memory is remaining */
int lfu_log_factor; /* LFU logarithmic counter factor. */ int lfu_log_factor; /* LFU logarithmic counter factor. */
int lfu_decay_time; /* LFU counter decay factor. */ int lfu_decay_time; /* LFU counter decay factor. */
long long proto_max_bulk_len; /* Protocol bulk length maximum size. */ long long proto_max_bulk_len; /* Protocol bulk length maximum size. */
@@ -2676,6 +2688,7 @@ struct redisServer {
int fActiveReplica; /* Can this replica also be a master? */ int fActiveReplica; /* Can this replica also be a master? */
int fWriteDuringActiveLoad; /* Can this active-replica write during an RDB load? */ int fWriteDuringActiveLoad; /* Can this active-replica write during an RDB load? */
int fEnableFastSync = false;
// Format: // Format:
// Lower 20 bits: a counter incrementing for each command executed in the same millisecond // Lower 20 bits: a counter incrementing for each command executed in the same millisecond
@@ -2700,6 +2713,7 @@ struct redisServer {
int tls_auth_clients; int tls_auth_clients;
int tls_rotation; int tls_rotation;
std::set<sdsstring> tls_auditlog_blocklist; /* Certificates that can be excluded from audit logging */
std::set<sdsstring> tls_allowlist; std::set<sdsstring> tls_allowlist;
redisTLSContextConfig tls_ctx_config; redisTLSContextConfig tls_ctx_config;
@@ -2728,14 +2742,24 @@ struct redisServer {
long long repl_batch_offStart = -1; long long repl_batch_offStart = -1;
long long repl_batch_idxStart = -1; long long repl_batch_idxStart = -1;
long long rand_total_threshold;
int config_soft_shutdown = false; int config_soft_shutdown = false;
bool soft_shutdown = false; bool soft_shutdown = false;
int flash_disable_key_cache = false;
/* Lock Contention Ring Buffer */ /* Lock Contention Ring Buffer */
static const size_t s_lockContentionSamples = 64; static const size_t s_lockContentionSamples = 64;
uint16_t rglockSamples[s_lockContentionSamples]; uint16_t rglockSamples[s_lockContentionSamples];
unsigned ilockRingHead = 0; unsigned ilockRingHead = 0;
sds sdsAvailabilityZone;
int overload_protect_threshold = 0;
int is_overloaded = 0;
int overload_closed_clients = 0;
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
client blocked on a module command needs client blocked on a module command needs
to be processed. */ to be processed. */
@@ -2850,6 +2874,12 @@ typedef struct {
#define OBJ_HASH_KEY 1 #define OBJ_HASH_KEY 1
#define OBJ_HASH_VALUE 2 #define OBJ_HASH_VALUE 2
/* Used in evict.cpp */
enum class EvictReason {
User, /* User memory exceeded limit */
System /* System memory exceeded limit */
};
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Extern declarations * Extern declarations
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
@@ -2890,7 +2920,7 @@ int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc,
moduleType *moduleTypeLookupModuleByID(uint64_t id); moduleType *moduleTypeLookupModuleByID(uint64_t id);
void moduleTypeNameByID(char *name, uint64_t moduleid); void moduleTypeNameByID(char *name, uint64_t moduleid);
const char *moduleTypeModuleName(moduleType *mt); const char *moduleTypeModuleName(moduleType *mt);
void moduleFreeContext(struct RedisModuleCtx *ctx); void moduleFreeContext(struct RedisModuleCtx *ctx, bool propogate = true);
void unblockClientFromModule(client *c); void unblockClientFromModule(client *c);
void moduleHandleBlockedClients(int iel); void moduleHandleBlockedClients(int iel);
void moduleBlockedClientTimedOut(client *c); void moduleBlockedClientTimedOut(client *c);
@@ -3085,6 +3115,7 @@ void queueMultiCommand(client *c);
void touchWatchedKey(redisDb *db, robj *key); void touchWatchedKey(redisDb *db, robj *key);
int isWatchedKeyExpired(client *c); int isWatchedKeyExpired(client *c);
void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with); void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with);
void updateDBWatchedKey(int dbid, client *c);
void discardTransaction(client *c); void discardTransaction(client *c);
void flagTransaction(client *c); void flagTransaction(client *c);
void execCommandAbort(client *c, sds error); void execCommandAbort(client *c, sds error);
@@ -3150,9 +3181,10 @@ int equalStringObjects(robj *a, robj *b);
unsigned long long estimateObjectIdleTime(robj_roptr o); unsigned long long estimateObjectIdleTime(robj_roptr o);
void trimStringObjectIfNeeded(robj *o); void trimStringObjectIfNeeded(robj *o);
robj *deserializeStoredObject(const redisDbPersistentData *db, const char *key, const void *data, size_t cb); robj *deserializeStoredObject(const void *data, size_t cb);
std::unique_ptr<expireEntry> deserializeExpire(sds key, const char *str, size_t cch, size_t *poffset); std::unique_ptr<expireEntry> deserializeExpire(const char *str, size_t cch, size_t *poffset);
sds serializeStoredObject(robj_roptr o, sds sdsPrefix = nullptr); sds serializeStoredObject(robj_roptr o, sds sdsPrefix = nullptr);
sds serializeStoredObjectAndExpire(robj_roptr o);
#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR) #define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
@@ -3254,6 +3286,7 @@ void receiveChildInfo(void);
void executeWithoutGlobalLock(std::function<void()> func); void executeWithoutGlobalLock(std::function<void()> func);
int redisFork(int type); int redisFork(int type);
int hasActiveChildProcess(); int hasActiveChildProcess();
int hasActiveChildProcessOrBGSave();
void resetChildState(); void resetChildState();
int isMutuallyExclusiveChildType(int type); int isMutuallyExclusiveChildType(int type);
@@ -3353,7 +3386,7 @@ int zslLexValueGteMin(sds value, zlexrangespec *spec);
int zslLexValueLteMax(sds value, zlexrangespec *spec); int zslLexValueLteMax(sds value, zlexrangespec *spec);
/* Core functions */ /* Core functions */
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level, bool fQuickCycle = false, bool fPreSnapshot=false); int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level, EvictReason *reason=nullptr, bool fQuickCycle=false, bool fPreSnapshot=false);
size_t freeMemoryGetNotCountedMemory(); size_t freeMemoryGetNotCountedMemory();
int overMaxmemoryAfterAlloc(size_t moremem); int overMaxmemoryAfterAlloc(size_t moremem);
int processCommand(client *c, int callFlags); int processCommand(client *c, int callFlags);
@@ -3638,6 +3671,9 @@ unsigned long LFUDecrAndReturn(robj_roptr o);
#define EVICT_FAIL 2 #define EVICT_FAIL 2
int performEvictions(bool fPreSnapshot); int performEvictions(bool fPreSnapshot);
/* meminfo.cpp -- get memory info from /proc/memoryinfo for linux distros */
size_t getMemAvailable();
size_t getMemTotal();
/* Keys hashing / comparison functions for dict.c hash tables. */ /* Keys hashing / comparison functions for dict.c hash tables. */
uint64_t dictSdsHash(const void *key); uint64_t dictSdsHash(const void *key);
@@ -3890,15 +3926,15 @@ void updateActiveReplicaMastersFromRsi(rdbSaveInfo *rsi);
uint64_t getMvccTstamp(); uint64_t getMvccTstamp();
void incrementMvccTstamp(); void incrementMvccTstamp();
#if __GNUC__ >= 7 && !defined(NO_DEPRECATE_FREE) #if __GNUC__ >= 7 && !defined(NO_DEPRECATE_FREE) && !defined(ALPINE)
[[deprecated]] [[deprecated]]
void *calloc(size_t count, size_t size); void *calloc(size_t count, size_t size) noexcept;
[[deprecated]] [[deprecated]]
void free(void *ptr); void free(void *ptr) noexcept;
[[deprecated]] [[deprecated]]
void *malloc(size_t size); void *malloc(size_t size) noexcept;
[[deprecated]] [[deprecated]]
void *realloc(void *ptr, size_t size); void *realloc(void *ptr, size_t size) noexcept;
#endif #endif
/* Debugging stuff */ /* Debugging stuff */
@@ -3938,6 +3974,10 @@ inline int ielFromEventLoop(const aeEventLoop *eventLoop)
return iel; return iel;
} }
inline bool FFastSyncEnabled() {
return g_pserver->fEnableFastSync && !g_pserver->fActiveReplica;
}
inline int FCorrectThread(client *c) inline int FCorrectThread(client *c)
{ {
return (c->conn == nullptr) return (c->conn == nullptr)
+7 -13
View File
@@ -74,6 +74,7 @@ const redisDbPersistentDataSnapshot *redisDbPersistentData::createSnapshot(uint6
spdb->m_fTrackingChanges = 0; spdb->m_fTrackingChanges = 0;
spdb->m_pdict = m_pdict; spdb->m_pdict = m_pdict;
spdb->m_pdictTombstone = m_pdictTombstone; spdb->m_pdictTombstone = m_pdictTombstone;
spdb->m_numexpires = m_numexpires;
// Add a fake iterator so the dicts don't rehash (they need to be read only) // Add a fake iterator so the dicts don't rehash (they need to be read only)
dictPauseRehashing(spdb->m_pdict); dictPauseRehashing(spdb->m_pdict);
dictForceRehash(spdb->m_pdictTombstone); // prevent rehashing by finishing the rehash now dictForceRehash(spdb->m_pdictTombstone); // prevent rehashing by finishing the rehash now
@@ -83,12 +84,6 @@ const redisDbPersistentDataSnapshot *redisDbPersistentData::createSnapshot(uint6
spdb->m_pdbSnapshot = m_pdbSnapshot; spdb->m_pdbSnapshot = m_pdbSnapshot;
spdb->m_refCount = 1; spdb->m_refCount = 1;
spdb->m_mvccCheckpoint = getMvccTstamp(); spdb->m_mvccCheckpoint = getMvccTstamp();
if (m_setexpire != nullptr)
{
std::unique_lock<fastlock> ul(g_expireLock);
spdb->m_setexpire = new (MALLOC_LOCAL) expireset(*m_setexpire);
spdb->m_setexpire->pause_rehash(); // needs to be const
}
if (dictIsRehashing(spdb->m_pdict) || dictIsRehashing(spdb->m_pdictTombstone)) { if (dictIsRehashing(spdb->m_pdict) || dictIsRehashing(spdb->m_pdictTombstone)) {
serverLog(LL_VERBOSE, "NOTICE: Suboptimal snapshot"); serverLog(LL_VERBOSE, "NOTICE: Suboptimal snapshot");
@@ -171,11 +166,6 @@ void redisDbPersistentData::restoreSnapshot(const redisDbPersistentDataSnapshot
size_t expectedSize = psnapshot->size(); size_t expectedSize = psnapshot->size();
dictEmpty(m_pdict, nullptr); dictEmpty(m_pdict, nullptr);
dictEmpty(m_pdictTombstone, nullptr); dictEmpty(m_pdictTombstone, nullptr);
{
std::unique_lock<fastlock> ul(g_expireLock);
delete m_setexpire;
m_setexpire = new (MALLOC_LOCAL) expireset(*psnapshot->m_setexpire);
}
endSnapshot(psnapshot); endSnapshot(psnapshot);
serverAssert(size() == expectedSize); serverAssert(size() == expectedSize);
} }
@@ -597,8 +587,12 @@ bool redisDbPersistentDataSnapshot::iterate_threadsafe_core(std::function<bool(c
if (!fKeyOnly) if (!fKeyOnly)
{ {
size_t offset = 0; size_t offset = 0;
deserializeExpire(sdsKey, (const char*)data, cbData, &offset); std::unique_ptr<expireEntry> spexpire = deserializeExpire((const char*)data, cbData, &offset);
o = deserializeStoredObject(this, sdsKey, reinterpret_cast<const char*>(data)+offset, cbData-offset); o = deserializeStoredObject(reinterpret_cast<const char*>(data)+offset, cbData-offset);
o->SetFExpires(spexpire != nullptr);
if (spexpire != nullptr) {
o->expire = std::move(*spexpire);
}
} }
fContinue = fn(sdsKey, o); fContinue = fn(sdsKey, o);
if (o != nullptr) if (o != nullptr)
+6 -4
View File
@@ -194,8 +194,8 @@ void sortCommand(client *c) {
list *operations; list *operations;
unsigned int outputlen = 0; unsigned int outputlen = 0;
int desc = 0, alpha = 0; int desc = 0, alpha = 0;
long limit_start = 0, limit_count = -1, start, end; long limit_start = 0, limit_count = -1, start, end, vectorlen;
int j, dontsort = 0, vectorlen; int j, dontsort = 0;
int getop = 0; /* GET operation counter */ int getop = 0; /* GET operation counter */
int int_conversion_error = 0; int int_conversion_error = 0;
int syntax_error = 0; int syntax_error = 0;
@@ -321,8 +321,10 @@ void sortCommand(client *c) {
default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */ default: vectorlen = 0; serverPanic("Bad SORT type"); /* Avoid GCC warning */
} }
/* Perform LIMIT start,count sanity checking. */ /* Perform LIMIT start,count sanity checking.
start = (limit_start < 0) ? 0 : limit_start; * And avoid integer overflow by limiting inputs to object sizes. */
start = std::min(std::max(limit_start, (long)0), vectorlen);
limit_count = std::min(std::max(limit_count, (long)-1), vectorlen);
end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
if (start >= vectorlen) { if (start >= vectorlen) {
start = vectorlen-1; start = vectorlen-1;
+157 -18
View File
@@ -3,8 +3,12 @@
#include <sstream> #include <sstream>
#include <mutex> #include <mutex>
#include <unistd.h> #include <unistd.h>
#include "../server.h"
#include "../cluster.h"
#include "rocksdbfactor_internal.h" #include "rocksdbfactor_internal.h"
template class std::basic_string<char>;
static const char keyprefix[] = INTERNAL_KEY_PREFIX; static const char keyprefix[] = INTERNAL_KEY_PREFIX;
rocksdb::Options DefaultRocksDBOptions(); rocksdb::Options DefaultRocksDBOptions();
@@ -20,8 +24,20 @@ bool FInternalKey(const char *key, size_t cch)
return false; return false;
} }
RocksDBStorageProvider::RocksDBStorageProvider(RocksDBStorageFactory *pfactory, std::shared_ptr<rocksdb::DB> &spdb, std::shared_ptr<rocksdb::ColumnFamilyHandle> &spcolfam, const rocksdb::Snapshot *psnapshot, size_t count) std::string getPrefix(unsigned int hashslot)
: m_pfactory(pfactory), m_spdb(spdb), m_psnapshot(psnapshot), m_spcolfamily(spcolfam), m_count(count) {
HASHSLOT_PREFIX_TYPE slot = HASHSLOT_PREFIX_ENDIAN((HASHSLOT_PREFIX_TYPE)hashslot);
char *hash_char = (char *)&slot;
return std::string(hash_char, HASHSLOT_PREFIX_BYTES);
}
std::string prefixKey(const char *key, size_t cchKey)
{
return FInternalKey(key, cchKey) ? std::string(key, cchKey) : getPrefix(keyHashSlot(key, cchKey)) + std::string(key, cchKey);
}
RocksDBStorageProvider::RocksDBStorageProvider(RocksDBStorageFactory *pfactory, std::shared_ptr<rocksdb::DB> &spdb, std::shared_ptr<rocksdb::ColumnFamilyHandle> &spcolfam, std::shared_ptr<rocksdb::ColumnFamilyHandle> &spexpirecolfam, const rocksdb::Snapshot *psnapshot, size_t count)
: m_pfactory(pfactory), m_spdb(spdb), m_psnapshot(psnapshot), m_spcolfamily(spcolfam), m_spexpirecolfamily(spexpirecolfam), m_count(count)
{ {
m_readOptionsTemplate = rocksdb::ReadOptions(); m_readOptionsTemplate = rocksdb::ReadOptions();
m_readOptionsTemplate.verify_checksums = false; m_readOptionsTemplate.verify_checksums = false;
@@ -32,10 +48,11 @@ void RocksDBStorageProvider::insert(const char *key, size_t cchKey, void *data,
{ {
rocksdb::Status status; rocksdb::Status status;
std::unique_lock<fastlock> l(m_lock); std::unique_lock<fastlock> l(m_lock);
std::string prefixed_key = prefixKey(key, cchKey);
if (m_spbatch != nullptr) if (m_spbatch != nullptr)
status = m_spbatch->Put(m_spcolfamily.get(), rocksdb::Slice(key, cchKey), rocksdb::Slice((const char*)data, cb)); status = m_spbatch->Put(m_spcolfamily.get(), rocksdb::Slice(prefixed_key), rocksdb::Slice((const char*)data, cb));
else else
status = m_spdb->Put(WriteOptions(), m_spcolfamily.get(), rocksdb::Slice(key, cchKey), rocksdb::Slice((const char*)data, cb)); status = m_spdb->Put(WriteOptions(), m_spcolfamily.get(), rocksdb::Slice(prefixed_key), rocksdb::Slice((const char*)data, cb));
if (!status.ok()) if (!status.ok())
throw status.ToString(); throw status.ToString();
@@ -59,7 +76,8 @@ void RocksDBStorageProvider::bulkInsert(char **rgkeys, size_t *rgcbkeys, char **
// Insert rows into the SST file, note that inserted keys must be // Insert rows into the SST file, note that inserted keys must be
// strictly increasing (based on options.comparator) // strictly increasing (based on options.comparator)
for (size_t ielem = 0; ielem < celem; ++ielem) { for (size_t ielem = 0; ielem < celem; ++ielem) {
s = sst_file_writer.Put(rocksdb::Slice(rgkeys[ielem], rgcbkeys[ielem]), rocksdb::Slice(rgvals[ielem], rgcbvals[ielem])); std::string prefixed_key = prefixKey(rgkeys[ielem], rgcbkeys[ielem]);
s = sst_file_writer.Put(rocksdb::Slice(prefixed_key), rocksdb::Slice(rgvals[ielem], rgcbvals[ielem]));
if (!s.ok()) { if (!s.ok()) {
unlink(file_path.c_str()); unlink(file_path.c_str());
goto LFallback; goto LFallback;
@@ -87,7 +105,8 @@ void RocksDBStorageProvider::bulkInsert(char **rgkeys, size_t *rgcbkeys, char **
LFallback: LFallback:
auto spbatch = std::make_unique<rocksdb::WriteBatch>(); auto spbatch = std::make_unique<rocksdb::WriteBatch>();
for (size_t ielem = 0; ielem < celem; ++ielem) { for (size_t ielem = 0; ielem < celem; ++ielem) {
spbatch->Put(m_spcolfamily.get(), rocksdb::Slice(rgkeys[ielem], rgcbkeys[ielem]), rocksdb::Slice(rgvals[ielem], rgcbvals[ielem])); std::string prefixed_key = prefixKey(rgkeys[ielem], rgcbkeys[ielem]);
spbatch->Put(m_spcolfamily.get(), rocksdb::Slice(prefixed_key), rocksdb::Slice(rgvals[ielem], rgcbvals[ielem]));
} }
m_spdb->Write(WriteOptions(), spbatch.get()); m_spdb->Write(WriteOptions(), spbatch.get());
} }
@@ -100,15 +119,16 @@ bool RocksDBStorageProvider::erase(const char *key, size_t cchKey)
{ {
rocksdb::Status status; rocksdb::Status status;
std::unique_lock<fastlock> l(m_lock); std::unique_lock<fastlock> l(m_lock);
std::string prefixed_key = prefixKey(key, cchKey);
if (!FKeyExists(prefixed_key))
return false;
if (m_spbatch != nullptr) if (m_spbatch != nullptr)
{ {
status = m_spbatch->Delete(m_spcolfamily.get(), rocksdb::Slice(key, cchKey)); status = m_spbatch->Delete(m_spcolfamily.get(), rocksdb::Slice(prefixed_key));
} }
else else
{ {
if (!FKeyExists(key, cchKey)) status = m_spdb->Delete(WriteOptions(), m_spcolfamily.get(), rocksdb::Slice(prefixed_key));
return false;
status = m_spdb->Delete(WriteOptions(), m_spcolfamily.get(), rocksdb::Slice(key, cchKey));
} }
if (status.ok()) if (status.ok())
--m_count; --m_count;
@@ -118,7 +138,8 @@ bool RocksDBStorageProvider::erase(const char *key, size_t cchKey)
void RocksDBStorageProvider::retrieve(const char *key, size_t cchKey, callbackSingle fn) const void RocksDBStorageProvider::retrieve(const char *key, size_t cchKey, callbackSingle fn) const
{ {
rocksdb::PinnableSlice slice; rocksdb::PinnableSlice slice;
auto status = m_spdb->Get(ReadOptions(), m_spcolfamily.get(), rocksdb::Slice(key, cchKey), &slice); std::string prefixed_key = prefixKey(key, cchKey);
auto status = m_spdb->Get(ReadOptions(), m_spcolfamily.get(), rocksdb::Slice(prefixed_key), &slice);
if (status.ok()) if (status.ok())
fn(key, cchKey, slice.data(), slice.size()); fn(key, cchKey, slice.data(), slice.size());
} }
@@ -126,16 +147,28 @@ void RocksDBStorageProvider::retrieve(const char *key, size_t cchKey, callbackSi
size_t RocksDBStorageProvider::clear() size_t RocksDBStorageProvider::clear()
{ {
size_t celem = count(); size_t celem = count();
auto options = m_spdb->GetOptions(m_spcolfamily.get());
auto status = m_spdb->DropColumnFamily(m_spcolfamily.get()); auto status = m_spdb->DropColumnFamily(m_spcolfamily.get());
auto strName = m_spcolfamily->GetName(); auto strName = m_spcolfamily->GetName();
rocksdb::ColumnFamilyHandle *handle = nullptr; rocksdb::ColumnFamilyHandle *handle = nullptr;
rocksdb::ColumnFamilyOptions cf_options(m_pfactory->RocksDbOptions()); rocksdb::ColumnFamilyOptions cf_options(options);
m_spdb->CreateColumnFamily(cf_options, strName, &handle); m_spdb->CreateColumnFamily(cf_options, strName, &handle);
m_spcolfamily = std::shared_ptr<rocksdb::ColumnFamilyHandle>(handle); m_spcolfamily = std::shared_ptr<rocksdb::ColumnFamilyHandle>(handle);
if (!status.ok()) if (!status.ok())
throw status.ToString(); throw status.ToString();
status = m_spdb->DropColumnFamily(m_spexpirecolfamily.get());
strName = m_spexpirecolfamily->GetName();
m_spdb->CreateColumnFamily(cf_options, strName, &handle);
m_spexpirecolfamily = std::shared_ptr<rocksdb::ColumnFamilyHandle>(handle);
if (!status.ok())
throw status.ToString();
m_count = 0; m_count = 0;
return celem; return celem;
} }
@@ -154,7 +187,7 @@ bool RocksDBStorageProvider::enumerate(callback fn) const
if (FInternalKey(it->key().data(), it->key().size())) if (FInternalKey(it->key().data(), it->key().size()))
continue; continue;
++count; ++count;
bool fContinue = fn(it->key().data(), it->key().size(), it->value().data(), it->value().size()); bool fContinue = fn(it->key().data()+HASHSLOT_PREFIX_BYTES, it->key().size()-HASHSLOT_PREFIX_BYTES, it->value().data(), it->value().size());
if (!fContinue) if (!fContinue)
break; break;
} }
@@ -168,11 +201,106 @@ bool RocksDBStorageProvider::enumerate(callback fn) const
return !it->Valid(); return !it->Valid();
} }
bool RocksDBStorageProvider::enumerate_hashslot(callback fn, unsigned int hashslot) const
{
std::string prefix = getPrefix(hashslot);
std::unique_ptr<rocksdb::Iterator> it = std::unique_ptr<rocksdb::Iterator>(m_spdb->NewIterator(ReadOptions(), m_spcolfamily.get()));
size_t count = 0;
for (it->Seek(prefix); it->Valid(); it->Next()) {
if (FInternalKey(it->key().data(), it->key().size()))
continue;
if (HASHSLOT_PREFIX_RECOVER(*(HASHSLOT_PREFIX_TYPE *)it->key().data()) != hashslot)
break;
++count;
bool fContinue = fn(it->key().data()+HASHSLOT_PREFIX_BYTES, it->key().size()-HASHSLOT_PREFIX_BYTES, it->value().data(), it->value().size());
if (!fContinue)
break;
}
bool full_iter = !it->Valid() || (HASHSLOT_PREFIX_RECOVER(*(HASHSLOT_PREFIX_TYPE *)it->key().data()) != hashslot);
if (full_iter && count != g_pserver->cluster->slots_keys_count[hashslot])
{
printf("WARNING: rocksdb hashslot count mismatch");
}
assert(!full_iter || count == g_pserver->cluster->slots_keys_count[hashslot]);
assert(it->status().ok()); // Check for any errors found during the scan
return full_iter;
}
void RocksDBStorageProvider::setExpire(const char *key, size_t cchKey, long long expire)
{
rocksdb::Status status;
std::unique_lock<fastlock> l(m_lock);
long long beExpire = htobe64(expire);
std::string prefix((const char *)&beExpire,sizeof(long long));
std::string strKey(key, cchKey);
if (m_spbatch != nullptr)
status = m_spbatch->Put(m_spexpirecolfamily.get(), rocksdb::Slice(prefix + strKey), rocksdb::Slice(strKey));
else
status = m_spdb->Put(WriteOptions(), m_spexpirecolfamily.get(), rocksdb::Slice(prefix + strKey), rocksdb::Slice(strKey));
if (!status.ok())
throw status.ToString();
}
void RocksDBStorageProvider::removeExpire(const char *key, size_t cchKey, long long expire)
{
rocksdb::Status status;
std::unique_lock<fastlock> l(m_lock);
long long beExpire = htobe64(expire);
std::string prefix((const char *)&beExpire,sizeof(long long));
std::string strKey(key, cchKey);
std::string fullKey = prefix + strKey;
if (!FExpireExists(fullKey))
return;
if (m_spbatch)
status = m_spbatch->Delete(m_spexpirecolfamily.get(), rocksdb::Slice(fullKey));
else
status = m_spdb->Delete(WriteOptions(), m_spexpirecolfamily.get(), rocksdb::Slice(fullKey));
if (!status.ok())
throw status.ToString();
}
std::vector<std::string> RocksDBStorageProvider::getExpirationCandidates(unsigned int count)
{
std::vector<std::string> result;
std::unique_ptr<rocksdb::Iterator> it = std::unique_ptr<rocksdb::Iterator>(m_spdb->NewIterator(ReadOptions(), m_spexpirecolfamily.get()));
for (it->SeekToFirst(); it->Valid() && result.size() < count; it->Next()) {
if (FInternalKey(it->key().data(), it->key().size()))
continue;
result.emplace_back(it->value().data(), it->value().size());
}
return result;
}
std::string randomHashSlot() {
return getPrefix(genrand64_int63() % (1 << 16));
}
std::vector<std::string> RocksDBStorageProvider::getEvictionCandidates(unsigned int count)
{
std::vector<std::string> result;
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
std::unique_ptr<rocksdb::Iterator> it = std::unique_ptr<rocksdb::Iterator>(m_spdb->NewIterator(ReadOptions(), m_spcolfamily.get()));
for (it->Seek(randomHashSlot()); it->Valid() && result.size() < count; it->Next()) {
if (FInternalKey(it->key().data(), it->key().size()))
continue;
result.emplace_back(it->key().data() + HASHSLOT_PREFIX_BYTES, it->key().size() - HASHSLOT_PREFIX_BYTES);
}
} else {
std::unique_ptr<rocksdb::Iterator> it = std::unique_ptr<rocksdb::Iterator>(m_spdb->NewIterator(ReadOptions(), m_spexpirecolfamily.get()));
for (it->SeekToFirst(); it->Valid() && result.size() < count; it->Next()) {
if (FInternalKey(it->key().data(), it->key().size()))
continue;
result.emplace_back(it->value().data(), it->value().size());
}
}
return result;
}
const IStorage *RocksDBStorageProvider::clone() const const IStorage *RocksDBStorageProvider::clone() const
{ {
std::unique_lock<fastlock> l(m_lock); std::unique_lock<fastlock> l(m_lock);
const rocksdb::Snapshot *psnapshot = const_cast<RocksDBStorageProvider*>(this)->m_spdb->GetSnapshot(); const rocksdb::Snapshot *psnapshot = const_cast<RocksDBStorageProvider*>(this)->m_spdb->GetSnapshot();
return new RocksDBStorageProvider(m_pfactory, const_cast<RocksDBStorageProvider*>(this)->m_spdb, const_cast<RocksDBStorageProvider*>(this)->m_spcolfamily, psnapshot, m_count); return new RocksDBStorageProvider(m_pfactory, const_cast<RocksDBStorageProvider*>(this)->m_spdb, const_cast<RocksDBStorageProvider*>(this)->m_spcolfamily, const_cast<RocksDBStorageProvider*>(this)->m_spexpirecolfamily, psnapshot, m_count);
} }
RocksDBStorageProvider::~RocksDBStorageProvider() RocksDBStorageProvider::~RocksDBStorageProvider()
@@ -202,12 +330,12 @@ rocksdb::WriteOptions RocksDBStorageProvider::WriteOptions() const
void RocksDBStorageProvider::beginWriteBatch() void RocksDBStorageProvider::beginWriteBatch()
{ {
m_lock.lock(); m_lock.lock();
m_spbatch = std::make_unique<rocksdb::WriteBatch>(); m_spbatch = std::make_unique<rocksdb::WriteBatchWithIndex>();
} }
void RocksDBStorageProvider::endWriteBatch() void RocksDBStorageProvider::endWriteBatch()
{ {
m_spdb->Write(WriteOptions(), m_spbatch.get()); m_spdb->Write(WriteOptions(), m_spbatch.get()->GetWriteBatch());
m_spbatch = nullptr; m_spbatch = nullptr;
m_lock.unlock(); m_lock.unlock();
} }
@@ -225,10 +353,21 @@ void RocksDBStorageProvider::batch_unlock()
void RocksDBStorageProvider::flush() void RocksDBStorageProvider::flush()
{ {
m_spdb->SyncWAL(); m_spdb->SyncWAL();
m_spdb->Flush(rocksdb::FlushOptions());
} }
bool RocksDBStorageProvider::FKeyExists(const char *key, size_t cch) const bool RocksDBStorageProvider::FKeyExists(std::string& key) const
{ {
rocksdb::PinnableSlice slice; rocksdb::PinnableSlice slice;
return m_spdb->Get(ReadOptions(), m_spcolfamily.get(), rocksdb::Slice(key, cch), &slice).ok(); if (m_spbatch)
return m_spbatch->GetFromBatchAndDB(m_spdb.get(), ReadOptions(), m_spcolfamily.get(), rocksdb::Slice(key), &slice).ok();
return m_spdb->Get(ReadOptions(), m_spcolfamily.get(), rocksdb::Slice(key), &slice).ok();
}
bool RocksDBStorageProvider::FExpireExists(std::string& key) const
{
rocksdb::PinnableSlice slice;
if (m_spbatch)
return m_spbatch->GetFromBatchAndDB(m_spdb.get(), ReadOptions(), m_spexpirecolfamily.get(), rocksdb::Slice(key), &slice).ok();
return m_spdb->Get(ReadOptions(), m_spexpirecolfamily.get(), rocksdb::Slice(key), &slice).ok();
} }
+25 -3
View File
@@ -3,27 +3,42 @@
#include <memory> #include <memory>
#include "../IStorage.h" #include "../IStorage.h"
#include <rocksdb/db.h> #include <rocksdb/db.h>
#include <rocksdb/utilities/write_batch_with_index.h>
#ifdef __APPLE__
#include <libkern/OSByteOrder.h>
#define htole16 OSSwapHostToLittleInt16
#define le16toh OSSwapLittleToHostInt16
#define htobe64 OSSwapHostToBigInt64
#else
#include <endian.h>
#endif
#include "../fastlock.h" #include "../fastlock.h"
#define INTERNAL_KEY_PREFIX "\x00\x04\x03\x00\x05\x02\x04" #define INTERNAL_KEY_PREFIX "\x00\x04\x03\x00\x05\x02\x04"
#define HASHSLOT_PREFIX_TYPE uint16_t
#define HASHSLOT_PREFIX_BYTES sizeof(HASHSLOT_PREFIX_TYPE)
#define HASHSLOT_PREFIX_ENDIAN htole16
#define HASHSLOT_PREFIX_RECOVER le16toh
static const char count_key[] = INTERNAL_KEY_PREFIX "__keydb__count\1"; static const char count_key[] = INTERNAL_KEY_PREFIX "__keydb__count\1";
static const char version_key[] = INTERNAL_KEY_PREFIX "__keydb__version\1"; static const char version_key[] = INTERNAL_KEY_PREFIX "__keydb__version\1";
static const char meta_key[] = INTERNAL_KEY_PREFIX "__keydb__metadata\1"; static const char meta_key[] = INTERNAL_KEY_PREFIX "__keydb__metadata\1";
static const char last_expire_key[] = INTERNAL_KEY_PREFIX "__keydb__last_expire_time";
class RocksDBStorageFactory; class RocksDBStorageFactory;
class RocksDBStorageProvider : public IStorage class RocksDBStorageProvider : public IStorage
{ {
RocksDBStorageFactory *m_pfactory; RocksDBStorageFactory *m_pfactory;
std::shared_ptr<rocksdb::DB> m_spdb; // Note: This must be first so it is deleted last std::shared_ptr<rocksdb::DB> m_spdb; // Note: This must be first so it is deleted last
std::unique_ptr<rocksdb::WriteBatch> m_spbatch; std::unique_ptr<rocksdb::WriteBatchWithIndex> m_spbatch;
const rocksdb::Snapshot *m_psnapshot = nullptr; const rocksdb::Snapshot *m_psnapshot = nullptr;
std::shared_ptr<rocksdb::ColumnFamilyHandle> m_spcolfamily; std::shared_ptr<rocksdb::ColumnFamilyHandle> m_spcolfamily;
std::shared_ptr<rocksdb::ColumnFamilyHandle> m_spexpirecolfamily;
rocksdb::ReadOptions m_readOptionsTemplate; rocksdb::ReadOptions m_readOptionsTemplate;
size_t m_count = 0; size_t m_count = 0;
mutable fastlock m_lock {"RocksDBStorageProvider"}; mutable fastlock m_lock {"RocksDBStorageProvider"};
public: public:
RocksDBStorageProvider(RocksDBStorageFactory *pfactory, std::shared_ptr<rocksdb::DB> &spdb, std::shared_ptr<rocksdb::ColumnFamilyHandle> &spcolfam, const rocksdb::Snapshot *psnapshot, size_t count); RocksDBStorageProvider(RocksDBStorageFactory *pfactory, std::shared_ptr<rocksdb::DB> &spdb, std::shared_ptr<rocksdb::ColumnFamilyHandle> &spcolfam, std::shared_ptr<rocksdb::ColumnFamilyHandle> &spexpirecolfam, const rocksdb::Snapshot *psnapshot, size_t count);
~RocksDBStorageProvider(); ~RocksDBStorageProvider();
virtual void insert(const char *key, size_t cchKey, void *data, size_t cb, bool fOverwrite) override; virtual void insert(const char *key, size_t cchKey, void *data, size_t cb, bool fOverwrite) override;
@@ -31,6 +46,12 @@ public:
virtual void retrieve(const char *key, size_t cchKey, callbackSingle fn) const override; virtual void retrieve(const char *key, size_t cchKey, callbackSingle fn) const override;
virtual size_t clear() override; virtual size_t clear() override;
virtual bool enumerate(callback fn) const override; virtual bool enumerate(callback fn) const override;
virtual bool enumerate_hashslot(callback fn, unsigned int hashslot) const override;
virtual std::vector<std::string> getExpirationCandidates(unsigned int count) override;
virtual std::vector<std::string> getEvictionCandidates(unsigned int count) override;
virtual void setExpire(const char *key, size_t cchKey, long long expire) override;
virtual void removeExpire(const char *key, size_t cchKey, long long expire) override;
virtual const IStorage *clone() const override; virtual const IStorage *clone() const override;
@@ -47,7 +68,8 @@ public:
size_t count() const override; size_t count() const override;
protected: protected:
bool FKeyExists(const char *key, size_t cchKey) const; bool FKeyExists(std::string&) const;
bool FExpireExists(std::string&) const;
const rocksdb::ReadOptions &ReadOptions() const { return m_readOptionsTemplate; } const rocksdb::ReadOptions &ReadOptions() const { return m_readOptionsTemplate; }
rocksdb::WriteOptions WriteOptions() const; rocksdb::WriteOptions WriteOptions() const;
+2
View File
@@ -5,6 +5,7 @@ class RocksDBStorageFactory : public IStorageFactory
{ {
std::shared_ptr<rocksdb::DB> m_spdb; // Note: This must be first so it is deleted last std::shared_ptr<rocksdb::DB> m_spdb; // Note: This must be first so it is deleted last
std::vector<std::unique_ptr<rocksdb::ColumnFamilyHandle>> m_vecspcols; std::vector<std::unique_ptr<rocksdb::ColumnFamilyHandle>> m_vecspcols;
std::vector<std::unique_ptr<rocksdb::ColumnFamilyHandle>> m_vecspexpirecols;
std::shared_ptr<rocksdb::SstFileManager> m_pfilemanager; std::shared_ptr<rocksdb::SstFileManager> m_pfilemanager;
std::string m_path; std::string m_path;
bool m_fCreatedTempFolder = false; bool m_fCreatedTempFolder = false;
@@ -18,6 +19,7 @@ public:
virtual const char *name() const override; virtual const char *name() const override;
virtual size_t totalDiskspaceUsed() const override; virtual size_t totalDiskspaceUsed() const override;
virtual sdsstring getInfo() const override;
virtual bool FSlow() const override { return true; } virtual bool FSlow() const override { return true; }
+47 -19
View File
@@ -9,6 +9,7 @@
#include "rocksdbfactor_internal.h" #include "rocksdbfactor_internal.h"
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/statvfs.h>
rocksdb::Options DefaultRocksDBOptions() { rocksdb::Options DefaultRocksDBOptions() {
rocksdb::Options options; rocksdb::Options options;
@@ -60,8 +61,8 @@ RocksDBStorageFactory::RocksDBStorageFactory(const char *dbfile, int dbnum, cons
auto status = rocksdb::DB::ListColumnFamilies(rocksdb::Options(), dbfile, &vecT); auto status = rocksdb::DB::ListColumnFamilies(rocksdb::Options(), dbfile, &vecT);
// RocksDB requires we know the count of col families before opening, if the user only wants to see less // RocksDB requires we know the count of col families before opening, if the user only wants to see less
// we still have to make room for all column family handles regardless // we still have to make room for all column family handles regardless
if (status.ok() && (int)vecT.size() > dbnum) if (status.ok() && (int)vecT.size()/2 > dbnum)
dbnum = (int)vecT.size(); dbnum = (int)vecT.size()/2;
std::vector<rocksdb::ColumnFamilyDescriptor> veccoldesc; std::vector<rocksdb::ColumnFamilyDescriptor> veccoldesc;
veccoldesc.push_back(rocksdb::ColumnFamilyDescriptor(rocksdb::kDefaultColumnFamilyName, rocksdb::ColumnFamilyOptions())); // ignore default col family veccoldesc.push_back(rocksdb::ColumnFamilyDescriptor(rocksdb::kDefaultColumnFamilyName, rocksdb::ColumnFamilyOptions())); // ignore default col family
@@ -71,12 +72,14 @@ RocksDBStorageFactory::RocksDBStorageFactory(const char *dbfile, int dbnum, cons
rocksdb::DB *db = nullptr; rocksdb::DB *db = nullptr;
auto options = RocksDbOptions(); auto options = RocksDbOptions();
options.prefix_extractor.reset(rocksdb::NewFixedPrefixTransform(HASHSLOT_PREFIX_BYTES));
for (int idb = 0; idb < dbnum; ++idb) for (int idb = 0; idb < dbnum; ++idb)
{ {
rocksdb::ColumnFamilyOptions cf_options(options); rocksdb::ColumnFamilyOptions cf_options(options);
cf_options.level_compaction_dynamic_level_bytes = true; cf_options.level_compaction_dynamic_level_bytes = true;
veccoldesc.push_back(rocksdb::ColumnFamilyDescriptor(std::to_string(idb), cf_options)); veccoldesc.push_back(rocksdb::ColumnFamilyDescriptor(std::to_string(idb), cf_options));
veccoldesc.push_back(rocksdb::ColumnFamilyDescriptor(std::to_string(idb) + "_expires", cf_options));
} }
if (rgchConfig != nullptr) if (rgchConfig != nullptr)
@@ -98,23 +101,29 @@ RocksDBStorageFactory::RocksDBStorageFactory(const char *dbfile, int dbnum, cons
m_spdb = std::shared_ptr<rocksdb::DB>(db); m_spdb = std::shared_ptr<rocksdb::DB>(db);
for (auto handle : handles) for (auto handle : handles)
{ {
std::string strVersion; if (handle->GetName().size() > 7 && !strncmp(handle->GetName().substr(handle->GetName().size() - 7).c_str(), "expires", 7)) {
auto status = m_spdb->Get(rocksdb::ReadOptions(), handle, rocksdb::Slice(version_key, sizeof(version_key)), &strVersion); m_vecspexpirecols.emplace_back(handle);
if (!status.ok()) } else {
{ std::string strVersion;
setVersion(handle); auto status = m_spdb->Get(rocksdb::ReadOptions(), handle, rocksdb::Slice(version_key, sizeof(version_key)), &strVersion);
} if (!status.ok())
else {
{
SymVer ver = parseVersion(strVersion.c_str());
auto cmp = compareVersion(&ver);
if (cmp == NewerVersion)
throw "Cannot load FLASH database created by newer version of KeyDB";
if (cmp == OlderVersion)
setVersion(handle); setVersion(handle);
}
else
{
SymVer ver = parseVersion(strVersion.c_str());
auto cmp = compareVersion(&ver);
if (cmp == NewerVersion)
throw "Cannot load FLASH database created by newer version of KeyDB";
if (cmp == IncompatibleVersion)
throw "Cannot load FLASH database from before 6.3.4";
if (cmp == OlderVersion)
setVersion(handle);
}
m_vecspcols.emplace_back(handle);
} }
m_vecspcols.emplace_back(handle); }
}
} }
RocksDBStorageFactory::~RocksDBStorageFactory() RocksDBStorageFactory::~RocksDBStorageFactory()
@@ -154,6 +163,7 @@ IStorage *RocksDBStorageFactory::create(int db, key_load_iterator iter, void *pr
{ {
++db; // skip default col family ++db; // skip default col family
std::shared_ptr<rocksdb::ColumnFamilyHandle> spcolfamily(m_vecspcols[db].release()); std::shared_ptr<rocksdb::ColumnFamilyHandle> spcolfamily(m_vecspcols[db].release());
std::shared_ptr<rocksdb::ColumnFamilyHandle> spexpirecolfamily(m_vecspexpirecols[db].release());
size_t count = 0; size_t count = 0;
bool fUnclean = false; bool fUnclean = false;
@@ -186,11 +196,11 @@ IStorage *RocksDBStorageFactory::create(int db, key_load_iterator iter, void *pr
printf("\tDatabase %d was not shutdown cleanly, recomputing metrics\n", db); printf("\tDatabase %d was not shutdown cleanly, recomputing metrics\n", db);
fFirstRealKey = false; fFirstRealKey = false;
if (iter != nullptr) if (iter != nullptr)
iter(it->key().data(), it->key().size(), privdata); iter(it->key().data()+HASHSLOT_PREFIX_BYTES, it->key().size()-HASHSLOT_PREFIX_BYTES, privdata);
++count; ++count;
} }
} }
return new RocksDBStorageProvider(this, m_spdb, spcolfamily, nullptr, count); return new RocksDBStorageProvider(this, m_spdb, spcolfamily, spexpirecolfamily, nullptr, count);
} }
const char *RocksDBStorageFactory::name() const const char *RocksDBStorageFactory::name() const
@@ -202,3 +212,21 @@ size_t RocksDBStorageFactory::totalDiskspaceUsed() const
{ {
return m_pfilemanager->GetTotalSize(); return m_pfilemanager->GetTotalSize();
} }
sdsstring RocksDBStorageFactory::getInfo() const
{
struct statvfs fiData;
int status = statvfs(m_path.c_str(), &fiData);
if ( status == 0 ) {
return sdsstring(sdscatprintf(sdsempty(),
"storage_flash_used_bytes:%zu\r\n"
"storage_flash_total_bytes:%zu\r\n"
"storage_flash_rocksdb_bytes:%zu\r\n",
fiData.f_bfree * fiData.f_frsize,
fiData.f_blocks * fiData.f_frsize,
totalDiskspaceUsed()));
} else {
fprintf(stderr, "Failed to gather FLASH statistics with status: %d\r\n", status);
return sdsstring(sdsempty());
}
}
+16 -1
View File
@@ -73,7 +73,22 @@ bool TestStorageProvider::enumerate(callback fn) const
} }
return fAll; return fAll;
} }
bool TestStorageProvider::enumerate_hashslot(callback fn, unsigned int hashslot) const
{
bool fAll = true;
for (auto &pair : m_map)
{
if (keyHashSlot(pair.first.data(), pair.first.size()) == hashslot)
if (!fn(pair.first.data(), pair.first.size(), pair.second.data(), pair.second.size()))
{
fAll = false;
break;
}
}
return fAll;
}
size_t TestStorageProvider::count() const size_t TestStorageProvider::count() const
{ {
return m_map.size(); return m_map.size();
+8
View File
@@ -1,6 +1,7 @@
#include "../IStorage.h" #include "../IStorage.h"
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector>
class TestStorageFactory : public IStorageFactory class TestStorageFactory : public IStorageFactory
{ {
@@ -8,6 +9,7 @@ class TestStorageFactory : public IStorageFactory
virtual class IStorage *createMetadataDb() override; virtual class IStorage *createMetadataDb() override;
virtual const char *name() const override; virtual const char *name() const override;
virtual size_t totalDiskspaceUsed() const override { return 0; } virtual size_t totalDiskspaceUsed() const override { return 0; }
virtual sdsstring getInfo() const override { return sdsstring(sdsempty()); }
virtual bool FSlow() const override { return false; } virtual bool FSlow() const override { return false; }
}; };
@@ -24,8 +26,14 @@ public:
virtual void retrieve(const char *key, size_t cchKey, callbackSingle fn) const override; virtual void retrieve(const char *key, size_t cchKey, callbackSingle fn) const override;
virtual size_t clear() override; virtual size_t clear() override;
virtual bool enumerate(callback fn) const override; virtual bool enumerate(callback fn) const override;
virtual bool enumerate_hashslot(callback fn, unsigned int hashslot) const override;
virtual size_t count() const override; virtual size_t count() const override;
virtual std::vector<std::string> getExpirationCandidates(unsigned int) override { return std::vector<std::string>(); }
virtual std::vector<std::string> getEvictionCandidates(unsigned int) override { return std::vector<std::string>(); }
virtual void setExpire(const char *, size_t, long long) override {}
virtual void removeExpire(const char *, size_t, long long) override {}
virtual void flush() override; virtual void flush() override;
/* This is permitted to be a shallow clone */ /* This is permitted to be a shallow clone */
Loaded 100 of 131 files, more files were not shown because too many files have changed in this diff. Show more