Unverified Commit c18ff056 authored by Moti Cohen's avatar Moti Cohen Committed by GitHub
Browse files

Hash Field Expiration - Basic support

- Add ebuckets & mstr data structures
- Integrate active & lazy expiration
- Add most of the commands 
- Add support for dict (listpack is missing)
TODOs:  RDB, notification, listpack, HSET, HGETF, defrag, aof
parent 4581d432
...@@ -745,7 +745,7 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) { ...@@ -745,7 +745,7 @@ int moduleDelKeyIfEmpty(RedisModuleKey *key) {
case OBJ_LIST: isempty = listTypeLength(o) == 0; break; case OBJ_LIST: isempty = listTypeLength(o) == 0; break;
case OBJ_SET: isempty = setTypeSize(o) == 0; break; case OBJ_SET: isempty = setTypeSize(o) == 0; break;
case OBJ_ZSET: isempty = zsetLength(o) == 0; break; case OBJ_ZSET: isempty = zsetLength(o) == 0; break;
case OBJ_HASH: isempty = hashTypeLength(o) == 0; break; case OBJ_HASH: isempty = hashTypeLength(o, 0) == 0; break;
case OBJ_STREAM: isempty = streamLength(o) == 0; break; case OBJ_STREAM: isempty = streamLength(o) == 0; break;
default: isempty = 0; default: isempty = 0;
} }
...@@ -4168,7 +4168,7 @@ size_t RM_ValueLength(RedisModuleKey *key) { ...@@ -4168,7 +4168,7 @@ size_t RM_ValueLength(RedisModuleKey *key) {
case OBJ_LIST: return listTypeLength(key->value); case OBJ_LIST: return listTypeLength(key->value);
case OBJ_SET: return setTypeSize(key->value); case OBJ_SET: return setTypeSize(key->value);
case OBJ_ZSET: return zsetLength(key->value); case OBJ_ZSET: return zsetLength(key->value);
case OBJ_HASH: return hashTypeLength(key->value); case OBJ_HASH: return hashTypeLength(key->value, 0); /* OPEN: To subtract expired fields? */
case OBJ_STREAM: return streamLength(key->value); case OBJ_STREAM: return streamLength(key->value);
default: return 0; default: return 0;
} }
...@@ -5296,7 +5296,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) { ...@@ -5296,7 +5296,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
   
robj *argv[2] = {field,value}; robj *argv[2] = {field,value};
hashTypeTryConversion(key->value,argv,0,1); hashTypeTryConversion(key->value,argv,0,1);
int updated = hashTypeSet(key->value, field->ptr, value->ptr, low_flags); int updated = hashTypeSet(key->db, key->value, field->ptr, value->ptr, low_flags);
count += (flags & REDISMODULE_HASH_COUNT_ALL) ? 1 : updated; count += (flags & REDISMODULE_HASH_COUNT_ALL) ? 1 : updated;
   
/* If CFIELDS is active, SDS string ownership is now of hashTypeSet(), /* If CFIELDS is active, SDS string ownership is now of hashTypeSet(),
...@@ -11071,18 +11071,22 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) { ...@@ -11071,18 +11071,22 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) {
ScanKeyCBData *data = privdata; ScanKeyCBData *data = privdata;
sds key = dictGetKey(de); sds key = dictGetKey(de);
robj *o = data->key->value; robj *o = data->key->value;
robj *field = createStringObject(key, sdslen(key)); robj *field = NULL;
robj *value = NULL; robj *value = NULL;
if (o->type == OBJ_SET) { if (o->type == OBJ_SET) {
value = NULL; value = NULL;
} else if (o->type == OBJ_HASH) { } else if (o->type == OBJ_HASH) {
sds val = dictGetVal(de); sds val = dictGetVal(de);
field = createStringObject(key, hfieldlen(key));
value = createStringObject(val, sdslen(val)); value = createStringObject(val, sdslen(val));
} else if (o->type == OBJ_ZSET) { } else if (o->type == OBJ_ZSET) {
double *val = (double*)dictGetVal(de); double *val = (double*)dictGetVal(de);
value = createStringObjectFromLongDouble(*val, 0); value = createStringObjectFromLongDouble(*val, 0);
} }
   
/* if type is OBJ_HASH then key is of type hfield. Otherwise sds. */
if (!field) field = createStringObject(key, sdslen(key));
data->fn(data->key, field, value, data->user_data); data->fn(data->key, field, value, data->user_data);
decrRefCount(field); decrRefCount(field);
if (value) decrRefCount(value); if (value) decrRefCount(value);
......
This diff is collapsed.
/*
* Copyright Redis Ltd. 2024 - present
*
* Licensed under your choice of the Redis Source Available License 2.0 (RSALv2)
* or the Server Side Public License v1 (SSPLv1).
*
*
* WHAT IS MSTR (M-STRING)?
* ------------------------
* mstr stands for immutable string with optional metadata attached.
*
* sds string is widely used across the system and serves as a general purpose
* container to hold data. The need to optimize memory and aggregate strings
* along with metadata and store it into Redis data-structures as single bulk keep
* reoccur. One thought might be, why not to extend sds to support metadata. The
* answer is that sds is mutable string in its nature, with wide API (split, join,
* etc.). Pushing metadata logic into sds will make it very fragile, and complex
* to maintain.
*
* Another idea involved using a simple struct with flags and a dynamic buf[] at the
* end. While this could be viable, it introduces considerable complexity and would
* need maintenance across different contexts.
*
* As an alternative, we introduce a new implementation of immutable strings,
* with limited API, and with the option to attach metadata. The representation
* of the string, without any metadata, in its basic form, resembles SDS but
* without the API to manipulate the string. Only to attach metadata to it. The
* following diagram shows the memory layout of mstring (mstrhdr8) when no
* metadata is attached:
*
* +----------------------------------------------+
* | mstrhdr8 | c-string | |
* +--------------------------------+-------------+
* |8b |2b |1b |5b |?bytes |8b|
* | Len | Type |m-bit=0 | Unused | String |\0|
* +----------------------------------------------+
* ^
* |
* mstrNew() returns pointer to here --+
*
* If metadata-flag is set, depicted in diagram above as m-bit in the diagram,
* then the header will be preceded with additional 16 bits of metadata flags such
* that if i'th bit is set, then the i'th metadata structure is attached to the
* mstring. The metadata layout and their sizes are defined by mstrKind structure
* (More below).
*
* The following diagram shows the memory layout of mstr (mstrhdr8) when 3 bits in mFlags
* are set to indicate that 3 fields of metadata are attached to the mstring at the
* beginning.
*
* +-------------------------------------------------------------------------------+
* | METADATA FIELDS | mflags | mstrhdr8 | c-string | |
* +-----------------------+--------+--------------------------------+-------------+
* |?bytes |?bytes |?bytes |16b |8b |2b |1b |5b |?bytes |8b|
* | Meta3 | Meta2 | Meta0 | 0x1101 | Len | Type |m-bit=1 | Unused | String |\0|
* +-------------------------------------------------------------------------------+
* ^
* |
* mstrNewWithMeta() returns pointer to here --+
*
* mstr allows to define different kinds (groups) of mstrings, each with its
* own unique metadata layout. For example, in case of hash-fields, all instances of
* it can optionally have TTL metadata attached to it. This is achieved by first
* prototyping a single mstrKind structure that defines the metadata layout and sizes
* of this specific kind. Now each hash-field instance has still the freedom to
* attach or not attach the metadata to it, and metadata flags (mFlags) of the
* instance will reflect this decision.
*
* In the future, the keys of Redis keyspace can be another kind of mstring that
* has TTL, LRU or even dictEntry metadata embedded into. Unlike vptr in c++, this
* struct won't be attached to mstring but will be passed as yet another argument
* to API, to save memory. In addition, each instance of a given mstrkind can hold
* any subset of metadata and the 8 bits of metadata-flags will reflect it.
*
* The following example shows how to define mstrKind for possible future keyspace
* that aggregates several keyspace related metadata into one compact, singly
* allocated, mstring.
*
* typedef enum HkeyMetaFlags {
* HKEY_META_VAL_REF_COUNT = 0, // refcount
* HKEY_META_VAL_REF = 1, // Val referenced
* HKEY_META_EXPIRE = 2, // TTL and more
* HKEY_META_TYPE_ENC_LRU = 3, // TYPE + LRU + ENC
* HKEY_META_DICT_ENT_NEXT = 4, // Next dict entry
* // Following two must be together and in this order
* HKEY_META_VAL_EMBED8 = 5, // Val embedded, max 7 bytes
* HKEY_META_VAL_EMBED16 = 6, // Val embedded, max 15 bytes (23 with EMBED8)
* } HkeyMetaFlags;
*
* mstrKind hkeyKind = {
* .name = "hkey",
* .metaSize[HKEY_META_VAL_REF_COUNT] = 4,
* .metaSize[HKEY_META_VAL_REF] = 8,
* .metaSize[HKEY_META_EXPIRE] = sizeof(ExpireMeta),
* .metaSize[HKEY_META_TYPE_ENC_LRU] = 8,
* .metaSize[HKEY_META_DICT_ENT_NEXT] = 8,
* .metaSize[HKEY_META_VAL_EMBED8] = 8,
* .metaSize[HKEY_META_VAL_EMBED16] = 16,
* };
*
* MSTR-ALIGNMENT
* --------------
* There are two types of alignments to take into consideration:
* 1. Alignment of the metadata.
* 2. Alignment of returned mstr pointer
*
* 1) As the metadatas layout are reversed to their enumeration, it is recommended
* to put metadata with "better" alignment first in memory layout (enumerated
* last) and the worst, or those that simply don't require any alignment will be
* last in memory layout (enumerated first). This is similar the to the applied
* consideration when defining new struct in C. Note also that each metadata
* might either be attached to mstr or not which complicates the design phase
* of a new mstrKind a little.
*
* In the example above, HKEY_META_VAL_REF_COUNT, with worst alignment of 4
* bytes, is enumerated first, and therefore, will be last in memory layout.
*
* 2) Few optimizations in Redis rely on the fact that sds address is always an odd
* pointer. We can achieve the same with a little effort. It was already taken
* care that all headers of type mstrhdrX has odd size. With that in mind, if
* a new kind of mstr is required to be limited to odd addresses, then we must
* make sure that sizes of all related metadatas that are defined in mstrKind
* are even in size.
*/
#ifndef __MSTR_H
#define __MSTR_H
#include <sys/types.h>
#include <stdarg.h>
#include <stdint.h>
/* Selective copy of ifndef from server.h instead of including it */
#ifndef static_assert
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
#endif
#define MSTR_TYPE_5 0
#define MSTR_TYPE_8 1
#define MSTR_TYPE_16 2
#define MSTR_TYPE_64 3
#define MSTR_TYPE_MASK 3
#define MSTR_TYPE_BITS 2
#define MSTR_META_MASK 4
#define MSTR_HDR(T,s) ((struct mstrhdr##T *)((s)-(sizeof(struct mstrhdr##T))))
#define MSTR_HDR_VAR(T,s) struct mstrhdr##T *sh = (void*)((s)-(sizeof(struct mstrhdr##T)));
#define MSTR_META_BITS 1 /* is metadata attached? */
#define MSTR_TYPE_5_LEN(f) ((f) >> (MSTR_TYPE_BITS + MSTR_META_BITS))
#define CREATE_MSTR_INFO(len, ismeta, type) ( (((len<<MSTR_META_BITS) + ismeta) << (MSTR_TYPE_BITS)) | type )
/* mimic plain c-string */
typedef char *mstr;
/* Flags that can be set on mstring to indicate for attached metadata. It is
* */
typedef uint16_t mstrFlags;
struct __attribute__ ((__packed__)) mstrhdr5 {
unsigned char info; /* 2 lsb of type, 1 metadata, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) mstrhdr8 {
uint8_t unused; /* To achieve odd size header (See comment above) */
uint8_t len;
unsigned char info; /* 2 lsb of type, 6 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) mstrhdr16 {
uint16_t len;
unsigned char info; /* 2 lsb of type, 6 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) mstrhdr64 {
uint64_t len;
unsigned char info; /* 2 lsb of type, 6 unused bits */
char buf[];
};
#define NUM_MSTR_FLAGS (sizeof(mstrFlags)*8)
/* mstrKind is used to define a kind (a group) of mstring with its own metadata layout */
typedef struct mstrKind {
const char *name;
int metaSize[NUM_MSTR_FLAGS];
} mstrKind;
mstr mstrNew(const char *initStr, size_t lenStr, int trymalloc);
mstr mstrNewWithMeta(struct mstrKind *kind, const char *initStr, size_t lenStr, mstrFlags flags, int trymalloc);
mstr mstrNewCopy(struct mstrKind *kind, mstr src, mstrFlags newFlags);
void *mstrGetAllocPtr(struct mstrKind *kind, mstr str);
void mstrFree(struct mstrKind *kind, mstr s);
mstrFlags *mstrFlagsRef(mstr s);
void *mstrMetaRef(mstr s, struct mstrKind *kind, int flagIdx);
size_t mstrlen(const mstr s);
/* return non-zero if metadata is attached to mstring */
static inline int mstrIsMetaAttached(mstr s) { return s[-1] & MSTR_META_MASK; }
/* return whether if a specific flag-index is set */
static inline int mstrGetFlag(mstr s, int flagIdx) { return *mstrFlagsRef(s) & (1 << flagIdx); }
/* See comment above about MSTR-ALIGNMENT(2) */
static_assert(sizeof(struct mstrhdr5 ) % 2 == 1, "must be odd");
static_assert(sizeof(struct mstrhdr8 ) % 2 == 1, "must be odd");
static_assert(sizeof(struct mstrhdr16 ) % 2 == 1, "must be odd");
static_assert(sizeof(struct mstrhdr64 ) % 2 == 1, "must be odd");
static_assert(sizeof(mstrFlags ) % 2 == 0, "must be even to keep mstr pointer odd");
#ifdef REDIS_TEST
int mstrTest(int argc, char *argv[], int flags);
#endif
#endif
...@@ -31,6 +31,14 @@ size_t sdsZmallocSize(sds s) { ...@@ -31,6 +31,14 @@ size_t sdsZmallocSize(sds s) {
return zmalloc_size(sh); return zmalloc_size(sh);
} }
/* Return the size consumed from the allocator, for the specified hfield with
* metadata (mstr), including internal fragmentation. This function is used in
* order to compute the client output buffer size. */
size_t hfieldZmallocSize(hfield s) {
void *sh = hfieldGetAllocPtr(s);
return zmalloc_size(sh);
}
/* Return the amount of memory used by the sds string at object->ptr /* Return the amount of memory used by the sds string at object->ptr
* for a string object. This includes internal fragmentation. */ * for a string object. This includes internal fragmentation. */
size_t getStringObjectSdsUsedMemory(robj *o) { size_t getStringObjectSdsUsedMemory(robj *o) {
......
...@@ -80,7 +80,7 @@ sds keyspaceEventsFlagsToString(int flags) { ...@@ -80,7 +80,7 @@ sds keyspaceEventsFlagsToString(int flags) {
* 'event' is a C string representing the event name. * 'event' is a C string representing the event name.
* 'key' is a Redis object representing the key name. * 'key' is a Redis object representing the key name.
* 'dbid' is the database ID where the key lives. */ * 'dbid' is the database ID where the key lives. */
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) { void notifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) {
sds chan; sds chan;
robj *chanobj, *eventobj; robj *chanobj, *eventobj;
int len = -1; int len = -1;
......
...@@ -979,7 +979,6 @@ size_t streamRadixTreeMemoryUsage(rax *rax) { ...@@ -979,7 +979,6 @@ size_t streamRadixTreeMemoryUsage(rax *rax) {
* are checked and averaged to estimate the total size. */ * are checked and averaged to estimate the total size. */
#define OBJ_COMPUTE_SIZE_DEF_SAMPLES 5 /* Default sample size. */ #define OBJ_COMPUTE_SIZE_DEF_SAMPLES 5 /* Default sample size. */
size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) { size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
sds ele, ele2;
dict *d; dict *d;
dictIterator *di; dictIterator *di;
struct dictEntry *de; struct dictEntry *de;
...@@ -1016,7 +1015,7 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) { ...@@ -1016,7 +1015,7 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
di = dictGetIterator(d); di = dictGetIterator(d);
asize = sizeof(*o)+sizeof(dict)+(sizeof(struct dictEntry*)*dictBuckets(d)); asize = sizeof(*o)+sizeof(dict)+(sizeof(struct dictEntry*)*dictBuckets(d));
while((de = dictNext(di)) != NULL && samples < sample_size) { while((de = dictNext(di)) != NULL && samples < sample_size) {
ele = dictGetKey(de); sds ele = dictGetKey(de);
elesize += dictEntryMemUsage() + sdsZmallocSize(ele); elesize += dictEntryMemUsage() + sdsZmallocSize(ele);
samples++; samples++;
} }
...@@ -1057,9 +1056,9 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) { ...@@ -1057,9 +1056,9 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
di = dictGetIterator(d); di = dictGetIterator(d);
asize = sizeof(*o)+sizeof(dict)+(sizeof(struct dictEntry*)*dictBuckets(d)); asize = sizeof(*o)+sizeof(dict)+(sizeof(struct dictEntry*)*dictBuckets(d));
while((de = dictNext(di)) != NULL && samples < sample_size) { while((de = dictNext(di)) != NULL && samples < sample_size) {
ele = dictGetKey(de); hfield ele = dictGetKey(de);
ele2 = dictGetVal(de); sds ele2 = dictGetVal(de);
elesize += sdsZmallocSize(ele) + sdsZmallocSize(ele2); elesize += hfieldZmallocSize(ele) + sdsZmallocSize(ele2);
elesize += dictEntryMemUsage(); elesize += dictEntryMemUsage();
samples++; samples++;
} }
......
...@@ -173,11 +173,16 @@ raxNode *raxNewNode(size_t children, int datafield) { ...@@ -173,11 +173,16 @@ raxNode *raxNewNode(size_t children, int datafield) {
/* Allocate a new rax and return its pointer. On out of memory the function /* Allocate a new rax and return its pointer. On out of memory the function
* returns NULL. */ * returns NULL. */
rax *raxNew(void) { rax *raxNew(void) {
rax *rax = rax_malloc(sizeof(*rax)); return raxNewWithMetadata(0);
}
/* Allocate a new rax with metadata */
rax *raxNewWithMetadata(int metaSize) {
rax *rax = rax_malloc(sizeof(*rax) + metaSize);
if (rax == NULL) return NULL; if (rax == NULL) return NULL;
rax->numele = 0; rax->numele = 0;
rax->numnodes = 1; rax->numnodes = 1;
rax->head = raxNewNode(0,0); rax->head = raxNewNode(0, 0);
if (rax->head == NULL) { if (rax->head == NULL) {
rax_free(rax); rax_free(rax);
return NULL; return NULL;
...@@ -1210,6 +1215,25 @@ void raxRecursiveFree(rax *rax, raxNode *n, void (*free_callback)(void*)) { ...@@ -1210,6 +1215,25 @@ void raxRecursiveFree(rax *rax, raxNode *n, void (*free_callback)(void*)) {
rax->numnodes--; rax->numnodes--;
} }
/* Same as raxRecursiveFree() with context argument */
void raxRecursiveFreeWithCtx(rax *rax, raxNode *n,
void (*free_callback)(void *item, void *ctx), void *ctx) {
debugnode("free traversing",n);
int numchildren = n->iscompr ? 1 : n->size;
raxNode **cp = raxNodeLastChildPtr(n);
while(numchildren--) {
raxNode *child;
memcpy(&child,cp,sizeof(child));
raxRecursiveFreeWithCtx(rax,child,free_callback, ctx);
cp--;
}
debugnode("free depth-first",n);
if (free_callback && n->iskey && !n->isnull)
free_callback(raxGetData(n), ctx);
rax_free(n);
rax->numnodes--;
}
/* Free a whole radix tree, calling the specified callback in order to /* Free a whole radix tree, calling the specified callback in order to
* free the auxiliary data. */ * free the auxiliary data. */
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)) { void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)) {
...@@ -1218,6 +1242,15 @@ void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)) { ...@@ -1218,6 +1242,15 @@ void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)) {
rax_free(rax); rax_free(rax);
} }
/* Free a whole radix tree, calling the specified callback in order to
* free the auxiliary data. */
void raxFreeWithCbAndContext(rax *rax,
void (*free_callback)(void *item, void *ctx), void *ctx) {
raxRecursiveFreeWithCtx(rax,rax->head,free_callback,ctx);
assert(rax->numnodes == 0);
rax_free(rax);
}
/* Free a whole radix tree. */ /* Free a whole radix tree. */
void raxFree(rax *rax) { void raxFree(rax *rax) {
raxFreeWithCallback(rax,NULL); raxFreeWithCallback(rax,NULL);
......
...@@ -113,6 +113,7 @@ typedef struct rax { ...@@ -113,6 +113,7 @@ typedef struct rax {
raxNode *head; raxNode *head;
uint64_t numele; uint64_t numele;
uint64_t numnodes; uint64_t numnodes;
void *metadata[];
} rax; } rax;
/* Stack data structure used by raxLowWalk() in order to, optionally, return /* Stack data structure used by raxLowWalk() in order to, optionally, return
...@@ -166,12 +167,16 @@ typedef struct raxIterator { ...@@ -166,12 +167,16 @@ typedef struct raxIterator {
/* Exported API. */ /* Exported API. */
rax *raxNew(void); rax *raxNew(void);
rax *raxNewWithMetadata(int metaSize);
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old); int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old); int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old); int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
int raxFind(rax *rax, unsigned char *s, size_t len, void **value); int raxFind(rax *rax, unsigned char *s, size_t len, void **value);
void raxFree(rax *rax); void raxFree(rax *rax);
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)); void raxFreeWithCallback(rax *rax, void (*free_callback)(void*));
void raxFreeWithCbAndContext(rax *rax,
void (*free_callback)(void *item, void *ctx),
void *ctx);
void raxStart(raxIterator *it, rax *rt); void raxStart(raxIterator *it, rax *rt);
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len); int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len);
int raxNext(raxIterator *it); int raxNext(raxIterator *it);
......
This diff is collapsed.
...@@ -105,6 +105,7 @@ ...@@ -105,6 +105,7 @@
#define RDB_LOAD_ENC (1<<0) #define RDB_LOAD_ENC (1<<0)
#define RDB_LOAD_PLAIN (1<<1) #define RDB_LOAD_PLAIN (1<<1)
#define RDB_LOAD_SDS (1<<2) #define RDB_LOAD_SDS (1<<2)
#define RDB_LOAD_HFLD (1<<3)
/* flags on the purpose of rdb save or load */ /* flags on the purpose of rdb save or load */
#define RDBFLAGS_NONE 0 /* No special RDB loading or saving. */ #define RDBFLAGS_NONE 0 /* No special RDB loading or saving. */
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
#include "syscheck.h" #include "syscheck.h"
#include "threads_mngr.h" #include "threads_mngr.h"
#include "fmtargs.h" #include "fmtargs.h"
#include "mstr.h"
#include "ebuckets.h"
#include <time.h> #include <time.h>
#include <signal.h> #include <signal.h>
...@@ -281,6 +283,18 @@ int dictSdsKeyCompare(dict *d, const void *key1, ...@@ -281,6 +283,18 @@ int dictSdsKeyCompare(dict *d, const void *key1,
return memcmp(key1, key2, l1) == 0; return memcmp(key1, key2, l1) == 0;
} }
int dictSdsMstrKeyCompare(dict *d, const void *sdsLookup, const void *mstrStored)
{
int l1,l2;
UNUSED(d);
l1 = sdslen((sds)sdsLookup);
l2 = hfieldlen((hfield)mstrStored);
if (l1 != l2) return 0;
return memcmp(sdsLookup, mstrStored, l1) == 0;
}
/* A case insensitive version used for the command lookup table and other /* A case insensitive version used for the command lookup table and other
* places where case insensitive non binary-safe comparison is needed. */ * places where case insensitive non binary-safe comparison is needed. */
int dictSdsKeyCaseCompare(dict *d, const void *key1, int dictSdsKeyCaseCompare(dict *d, const void *key1,
...@@ -2500,6 +2514,7 @@ void resetServerStats(void) { ...@@ -2500,6 +2514,7 @@ void resetServerStats(void) {
server.stat_numcommands = 0; server.stat_numcommands = 0;
server.stat_numconnections = 0; server.stat_numconnections = 0;
server.stat_expiredkeys = 0; server.stat_expiredkeys = 0;
server.stat_expired_hash_fields = 0;
server.stat_expired_stale_perc = 0; server.stat_expired_stale_perc = 0;
server.stat_expired_time_cap_reached_count = 0; server.stat_expired_time_cap_reached_count = 0;
server.stat_expire_cycle_time_used = 0; server.stat_expire_cycle_time_used = 0;
...@@ -2648,6 +2663,7 @@ void initServer(void) { ...@@ -2648,6 +2663,7 @@ void initServer(void) {
for (j = 0; j < server.dbnum; j++) { for (j = 0; j < server.dbnum; j++) {
server.db[j].keys = kvstoreCreate(&dbDictType, slot_count_bits, flags); server.db[j].keys = kvstoreCreate(&dbDictType, slot_count_bits, flags);
server.db[j].expires = kvstoreCreate(&dbExpiresDictType, slot_count_bits, flags); server.db[j].expires = kvstoreCreate(&dbExpiresDictType, slot_count_bits, flags);
server.db[j].hexpires = ebCreate();
server.db[j].expires_cursor = 0; server.db[j].expires_cursor = 0;
server.db[j].blocking_keys = dictCreate(&keylistDictType); server.db[j].blocking_keys = dictCreate(&keylistDictType);
server.db[j].blocking_keys_unblock_on_nokey = dictCreate(&objectKeyPointerValueDictType); server.db[j].blocking_keys_unblock_on_nokey = dictCreate(&objectKeyPointerValueDictType);
...@@ -5849,6 +5865,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) { ...@@ -5849,6 +5865,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
"sync_full:%lld\r\n", server.stat_sync_full, "sync_full:%lld\r\n", server.stat_sync_full,
"sync_partial_ok:%lld\r\n", server.stat_sync_partial_ok, "sync_partial_ok:%lld\r\n", server.stat_sync_partial_ok,
"sync_partial_err:%lld\r\n", server.stat_sync_partial_err, "sync_partial_err:%lld\r\n", server.stat_sync_partial_err,
"expired_hash_fields:%lld\r\n", server.stat_expired_hash_fields,
"expired_keys:%lld\r\n", server.stat_expiredkeys, "expired_keys:%lld\r\n", server.stat_expiredkeys,
"expired_stale_perc:%.2f\r\n", server.stat_expired_stale_perc*100, "expired_stale_perc:%.2f\r\n", server.stat_expired_stale_perc*100,
"expired_time_cap_reached_count:%lld\r\n", server.stat_expired_time_cap_reached_count, "expired_time_cap_reached_count:%lld\r\n", server.stat_expired_time_cap_reached_count,
...@@ -6862,9 +6879,11 @@ struct redisTest { ...@@ -6862,9 +6879,11 @@ struct redisTest {
{"crc64", crc64Test}, {"crc64", crc64Test},
{"zmalloc", zmalloc_test}, {"zmalloc", zmalloc_test},
{"sds", sdsTest}, {"sds", sdsTest},
{"mstr", mstrTest},
{"dict", dictTest}, {"dict", dictTest},
{"listpack", listpackTest}, {"listpack", listpackTest},
{"kvstore", kvstoreTest}, {"kvstore", kvstoreTest},
{"ebuckets", ebucketsTest},
}; };
redisTestProc *getTestProcByName(const char *name) { redisTestProc *getTestProcByName(const char *name) {
int numtests = sizeof(redisTests)/sizeof(struct redisTest); int numtests = sizeof(redisTests)/sizeof(struct redisTest);
...@@ -6891,6 +6910,7 @@ int main(int argc, char **argv) { ...@@ -6891,6 +6910,7 @@ int main(int argc, char **argv) {
if (!strcasecmp(arg, "--accurate")) flags |= REDIS_TEST_ACCURATE; if (!strcasecmp(arg, "--accurate")) flags |= REDIS_TEST_ACCURATE;
else if (!strcasecmp(arg, "--large-memory")) flags |= REDIS_TEST_LARGE_MEMORY; else if (!strcasecmp(arg, "--large-memory")) flags |= REDIS_TEST_LARGE_MEMORY;
else if (!strcasecmp(arg, "--valgrind")) flags |= REDIS_TEST_VALGRIND; else if (!strcasecmp(arg, "--valgrind")) flags |= REDIS_TEST_VALGRIND;
else if (!strcasecmp(arg, "--verbose")) flags |= REDIS_TEST_VERBOSE;
} }
if (!strcasecmp(argv[2], "all")) { if (!strcasecmp(argv[2], "all")) {
......
This diff is collapsed.
This diff is collapsed.
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
#define REDIS_TEST_ACCURATE (1<<0) #define REDIS_TEST_ACCURATE (1<<0)
#define REDIS_TEST_LARGE_MEMORY (1<<1) #define REDIS_TEST_LARGE_MEMORY (1<<1)
#define REDIS_TEST_VALGRIND (1<<2) #define REDIS_TEST_VALGRIND (1<<2)
#define REDIS_TEST_VERBOSE (1<<3)
extern int __failed_tests; extern int __failed_tests;
extern int __test_num; extern int __test_num;
......
...@@ -34,6 +34,7 @@ set ::all_tests { ...@@ -34,6 +34,7 @@ set ::all_tests {
unit/type/set unit/type/set
unit/type/zset unit/type/zset
unit/type/hash unit/type/hash
unit/type/hash-field-expire
unit/type/stream unit/type/stream
unit/type/stream-cgroups unit/type/stream-cgroups
unit/sort unit/sort
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment