mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 08:08:53 -05:00
Implement module api for aux data in rdb
Other changes: * fix memory leak in error handling of rdb loading of type OBJ_MODULE
This commit is contained in:
parent
bc5cb168f5
commit
3b6aeea44c
@ -13,4 +13,4 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
make -C tests/modules && \
|
make -C tests/modules && \
|
||||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter "${@}"
|
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
|
||||||
|
41
src/module.c
41
src/module.c
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "cluster.h"
|
#include "cluster.h"
|
||||||
|
#include "rdb.h"
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
||||||
#define REDISMODULE_CORE 1
|
#define REDISMODULE_CORE 1
|
||||||
@ -3078,6 +3079,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
|
|||||||
moduleTypeMemUsageFunc mem_usage;
|
moduleTypeMemUsageFunc mem_usage;
|
||||||
moduleTypeDigestFunc digest;
|
moduleTypeDigestFunc digest;
|
||||||
moduleTypeFreeFunc free;
|
moduleTypeFreeFunc free;
|
||||||
|
struct {
|
||||||
|
moduleTypeAuxLoadFunc aux_load;
|
||||||
|
moduleTypeAuxSaveFunc aux_save;
|
||||||
|
int aux_save_triggers;
|
||||||
|
} v2;
|
||||||
} *tms = (struct typemethods*) typemethods_ptr;
|
} *tms = (struct typemethods*) typemethods_ptr;
|
||||||
|
|
||||||
moduleType *mt = zcalloc(sizeof(*mt));
|
moduleType *mt = zcalloc(sizeof(*mt));
|
||||||
@ -3089,6 +3095,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
|
|||||||
mt->mem_usage = tms->mem_usage;
|
mt->mem_usage = tms->mem_usage;
|
||||||
mt->digest = tms->digest;
|
mt->digest = tms->digest;
|
||||||
mt->free = tms->free;
|
mt->free = tms->free;
|
||||||
|
if (tms->version >= 2) {
|
||||||
|
mt->aux_load = tms->v2.aux_load;
|
||||||
|
mt->aux_save = tms->v2.aux_save;
|
||||||
|
mt->aux_save_triggers = tms->v2.aux_save_triggers;
|
||||||
|
}
|
||||||
memcpy(mt->name,name,sizeof(mt->name));
|
memcpy(mt->name,name,sizeof(mt->name));
|
||||||
listAddNodeTail(ctx->module->types,mt);
|
listAddNodeTail(ctx->module->types,mt);
|
||||||
return mt;
|
return mt;
|
||||||
@ -3355,6 +3366,36 @@ loaderr:
|
|||||||
return 0; /* Never reached. */
|
return 0; /* Never reached. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Iterate over modules, and trigger rdb aux saving for the ones modules types
|
||||||
|
* who asked for it. */
|
||||||
|
ssize_t rdbSaveModulesAux(rio *rdb, int when) {
|
||||||
|
size_t total_written = 0;
|
||||||
|
dictIterator *di = dictGetIterator(modules);
|
||||||
|
dictEntry *de;
|
||||||
|
|
||||||
|
while ((de = dictNext(di)) != NULL) {
|
||||||
|
struct RedisModule *module = dictGetVal(de);
|
||||||
|
listIter li;
|
||||||
|
listNode *ln;
|
||||||
|
|
||||||
|
listRewind(module->types,&li);
|
||||||
|
while((ln = listNext(&li))) {
|
||||||
|
moduleType *mt = ln->value;
|
||||||
|
if (!mt->aux_save || !(mt->aux_save_triggers & when))
|
||||||
|
continue;
|
||||||
|
ssize_t ret = rdbSaveSingleModuleAux(rdb, when, mt);
|
||||||
|
if (ret==-1) {
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
total_written += ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return total_written;
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
* Key digest API (DEBUG DIGEST interface for modules types)
|
* Key digest API (DEBUG DIGEST interface for modules types)
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
|
83
src/rdb.c
83
src/rdb.c
@ -971,7 +971,6 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
|||||||
RedisModuleIO io;
|
RedisModuleIO io;
|
||||||
moduleValue *mv = o->ptr;
|
moduleValue *mv = o->ptr;
|
||||||
moduleType *mt = mv->type;
|
moduleType *mt = mv->type;
|
||||||
moduleInitIOContext(io,mt,rdb,key);
|
|
||||||
|
|
||||||
/* Write the "module" identifier as prefix, so that we'll be able
|
/* Write the "module" identifier as prefix, so that we'll be able
|
||||||
* to call the right module during loading. */
|
* to call the right module during loading. */
|
||||||
@ -980,9 +979,12 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
|||||||
io.bytes += retval;
|
io.bytes += retval;
|
||||||
|
|
||||||
/* Then write the module-specific representation + EOF marker. */
|
/* Then write the module-specific representation + EOF marker. */
|
||||||
|
moduleInitIOContext(io,mt,rdb,key);
|
||||||
mt->rdb_save(&io,mv->value);
|
mt->rdb_save(&io,mv->value);
|
||||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||||
if (retval == -1) return -1;
|
if (retval == -1)
|
||||||
|
io.error = 1;
|
||||||
|
else
|
||||||
io.bytes += retval;
|
io.bytes += retval;
|
||||||
|
|
||||||
if (io.ctx) {
|
if (io.ctx) {
|
||||||
@ -1101,6 +1103,40 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
|
||||||
|
/* Save a module-specific aux value. */
|
||||||
|
RedisModuleIO io;
|
||||||
|
int retval = rdbSaveType(rdb, RDB_OPCODE_MODULE_AUX);
|
||||||
|
|
||||||
|
/* Write the "module" identifier as prefix, so that we'll be able
|
||||||
|
* to call the right module during loading. */
|
||||||
|
retval = rdbSaveLen(rdb,mt->id);
|
||||||
|
if (retval == -1) return -1;
|
||||||
|
io.bytes += retval;
|
||||||
|
|
||||||
|
/* write the 'when' so that we can provide it on loading */
|
||||||
|
retval = rdbSaveLen(rdb,when);
|
||||||
|
if (retval == -1) return -1;
|
||||||
|
io.bytes += retval;
|
||||||
|
|
||||||
|
/* Then write the module-specific representation + EOF marker. */
|
||||||
|
moduleInitIOContext(io,mt,rdb,NULL);
|
||||||
|
mt->aux_save(&io,when);
|
||||||
|
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||||
|
if (retval == -1)
|
||||||
|
io.error = 1;
|
||||||
|
else
|
||||||
|
io.bytes += retval;
|
||||||
|
|
||||||
|
if (io.ctx) {
|
||||||
|
moduleFreeContext(io.ctx);
|
||||||
|
zfree(io.ctx);
|
||||||
|
}
|
||||||
|
if (io.error)
|
||||||
|
return -1;
|
||||||
|
return io.bytes;
|
||||||
|
}
|
||||||
|
|
||||||
/* Produces a dump of the database in RDB format sending it to the specified
|
/* Produces a dump of the database in RDB format sending it to the specified
|
||||||
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR
|
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR
|
||||||
* is returned and part of the output, or all the output, can be
|
* is returned and part of the output, or all the output, can be
|
||||||
@ -1122,6 +1158,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
|||||||
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
||||||
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
||||||
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
|
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
|
||||||
|
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
|
||||||
|
|
||||||
for (j = 0; j < server.dbnum; j++) {
|
for (j = 0; j < server.dbnum; j++) {
|
||||||
redisDb *db = server.db+j;
|
redisDb *db = server.db+j;
|
||||||
@ -1183,6 +1220,8 @@ int rdbSaveRio(rio *rdb, int *error, int flags, 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. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
|
||||||
|
|
||||||
/* EOF opcode */
|
/* EOF opcode */
|
||||||
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
||||||
|
|
||||||
@ -2089,15 +2128,11 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
|||||||
decrRefCount(auxval);
|
decrRefCount(auxval);
|
||||||
continue; /* Read type again. */
|
continue; /* Read type again. */
|
||||||
} else if (type == RDB_OPCODE_MODULE_AUX) {
|
} else if (type == RDB_OPCODE_MODULE_AUX) {
|
||||||
/* This is just for compatibility with the future: we have plans
|
/* Load module data that is not related to the Redis key space.
|
||||||
* to add the ability for modules to store anything in the RDB
|
* Such data can be potentially be stored both before and after the
|
||||||
* file, like data that is not related to the Redis key space.
|
* RDB keys-values section. */
|
||||||
* Such data will potentially be stored both before and after the
|
|
||||||
* RDB keys-values section. For this reason since RDB version 9,
|
|
||||||
* we have the ability to read a MODULE_AUX opcode followed by an
|
|
||||||
* identifier of the module, and a serialized value in "MODULE V2"
|
|
||||||
* format. */
|
|
||||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||||
|
int when = rdbLoadLen(rdb,NULL);
|
||||||
if (rioGetReadError(rdb)) goto eoferr;
|
if (rioGetReadError(rdb)) goto eoferr;
|
||||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||||
char name[10];
|
char name[10];
|
||||||
@ -2108,10 +2143,32 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
|||||||
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
|
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
|
||||||
exit(1);
|
exit(1);
|
||||||
} else if (!rdbCheckMode && mt != NULL) {
|
} else if (!rdbCheckMode && mt != NULL) {
|
||||||
/* This version of Redis actually does not know what to do
|
if (!mt->aux_load) {
|
||||||
* with modules AUX data... */
|
/* Module doesn't support AUX. */
|
||||||
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load for the module '%s'. Probably you want to use a newer version of Redis which implements aux data callbacks", name);
|
serverLog(LL_WARNING,"The RDB file contains module AUX data, but the module '%s' doesn't seem to support it.", name);
|
||||||
exit(1);
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModuleIO io;
|
||||||
|
moduleInitIOContext(io,mt,rdb,NULL);
|
||||||
|
io.ver = 2;
|
||||||
|
/* Call the rdb_load method of the module providing the 10 bit
|
||||||
|
* encoding version in the lower 10 bits of the module ID. */
|
||||||
|
if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
|
||||||
|
moduleTypeNameByID(name,moduleid);
|
||||||
|
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (io.ctx) {
|
||||||
|
moduleFreeContext(io.ctx);
|
||||||
|
zfree(io.ctx);
|
||||||
|
}
|
||||||
|
uint64_t eof = rdbLoadLen(rdb,NULL);
|
||||||
|
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||||
|
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
} else {
|
} else {
|
||||||
/* RDB check mode. */
|
/* RDB check mode. */
|
||||||
robj *aux = rdbLoadCheckModuleValue(rdb,name);
|
robj *aux = rdbLoadCheckModuleValue(rdb,name);
|
||||||
|
@ -145,6 +145,7 @@ size_t rdbSavedObjectLen(robj *o);
|
|||||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||||
|
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||||
robj *rdbLoadStringObject(rio *rdb);
|
robj *rdbLoadStringObject(rio *rdb);
|
||||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
||||||
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
||||||
|
@ -129,6 +129,10 @@
|
|||||||
|
|
||||||
#define REDISMODULE_NOT_USED(V) ((void) V)
|
#define REDISMODULE_NOT_USED(V) ((void) V)
|
||||||
|
|
||||||
|
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
|
||||||
|
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||||
|
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||||
|
|
||||||
/* This type represents a timer handle, and is returned when a timer is
|
/* This type represents a timer handle, and is returned when a timer is
|
||||||
* registered and used in order to invalidate a timer. It's just a 64 bit
|
* registered and used in order to invalidate a timer. It's just a 64 bit
|
||||||
* number, because this is how each timer is represented inside the radix tree
|
* number, because this is how each timer is represented inside the radix tree
|
||||||
@ -166,6 +170,8 @@ typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlocke
|
|||||||
typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||||
|
typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int when);
|
||||||
|
typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when);
|
||||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||||
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
|
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
|
||||||
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
|
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
|
||||||
@ -174,7 +180,7 @@ typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const cha
|
|||||||
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||||
|
|
||||||
#define REDISMODULE_TYPE_METHOD_VERSION 1
|
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||||
typedef struct RedisModuleTypeMethods {
|
typedef struct RedisModuleTypeMethods {
|
||||||
uint64_t version;
|
uint64_t version;
|
||||||
RedisModuleTypeLoadFunc rdb_load;
|
RedisModuleTypeLoadFunc rdb_load;
|
||||||
@ -183,6 +189,9 @@ typedef struct RedisModuleTypeMethods {
|
|||||||
RedisModuleTypeMemUsageFunc mem_usage;
|
RedisModuleTypeMemUsageFunc mem_usage;
|
||||||
RedisModuleTypeDigestFunc digest;
|
RedisModuleTypeDigestFunc digest;
|
||||||
RedisModuleTypeFreeFunc free;
|
RedisModuleTypeFreeFunc free;
|
||||||
|
RedisModuleTypeAuxLoadFunc aux_load;
|
||||||
|
RedisModuleTypeAuxSaveFunc aux_save;
|
||||||
|
int aux_save_triggers;
|
||||||
} RedisModuleTypeMethods;
|
} RedisModuleTypeMethods;
|
||||||
|
|
||||||
#define REDISMODULE_GET_API(name) \
|
#define REDISMODULE_GET_API(name) \
|
||||||
|
10
src/server.h
10
src/server.h
@ -536,6 +536,10 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK)
|
#define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK)
|
||||||
#define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS)
|
#define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS)
|
||||||
|
|
||||||
|
/* Bit flags for moduleTypeAuxSaveFunc */
|
||||||
|
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
|
||||||
|
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
|
||||||
|
|
||||||
struct RedisModule;
|
struct RedisModule;
|
||||||
struct RedisModuleIO;
|
struct RedisModuleIO;
|
||||||
struct RedisModuleDigest;
|
struct RedisModuleDigest;
|
||||||
@ -548,6 +552,8 @@ struct redisObject;
|
|||||||
* is deleted. */
|
* is deleted. */
|
||||||
typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
|
typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
|
||||||
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
|
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
|
||||||
|
typedef int (*moduleTypeAuxLoadFunc)(struct RedisModuleIO *rdb, int encver, int when);
|
||||||
|
typedef void (*moduleTypeAuxSaveFunc)(struct RedisModuleIO *rdb, int when);
|
||||||
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
|
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
|
||||||
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
|
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
|
||||||
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
||||||
@ -564,6 +570,9 @@ typedef struct RedisModuleType {
|
|||||||
moduleTypeMemUsageFunc mem_usage;
|
moduleTypeMemUsageFunc mem_usage;
|
||||||
moduleTypeDigestFunc digest;
|
moduleTypeDigestFunc digest;
|
||||||
moduleTypeFreeFunc free;
|
moduleTypeFreeFunc free;
|
||||||
|
moduleTypeAuxLoadFunc aux_load;
|
||||||
|
moduleTypeAuxSaveFunc aux_save;
|
||||||
|
int aux_save_triggers;
|
||||||
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
|
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
|
||||||
} moduleType;
|
} moduleType;
|
||||||
|
|
||||||
@ -1528,6 +1537,7 @@ void moduleAcquireGIL(void);
|
|||||||
void moduleReleaseGIL(void);
|
void moduleReleaseGIL(void);
|
||||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||||
void moduleCallCommandFilters(client *c);
|
void moduleCallCommandFilters(client *c);
|
||||||
|
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
||||||
|
|
||||||
/* Utils */
|
/* Utils */
|
||||||
long long ustime(void);
|
long long ustime(void);
|
||||||
|
@ -13,12 +13,16 @@ endif
|
|||||||
|
|
||||||
.SUFFIXES: .c .so .xo .o
|
.SUFFIXES: .c .so .xo .o
|
||||||
|
|
||||||
all: commandfilter.so
|
all: commandfilter.so testrdb.so
|
||||||
|
|
||||||
.c.xo:
|
.c.xo:
|
||||||
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||||
|
|
||||||
commandfilter.xo: ../../src/redismodule.h
|
commandfilter.xo: ../../src/redismodule.h
|
||||||
|
testrdb.xo: ../../src/redismodule.h
|
||||||
|
|
||||||
commandfilter.so: commandfilter.xo
|
commandfilter.so: commandfilter.xo
|
||||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
|
||||||
|
testrdb.so: testrdb.xo
|
||||||
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
229
tests/modules/testrdb.c
Normal file
229
tests/modules/testrdb.c
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
#include "redismodule.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/* Module configuration, save aux or not? */
|
||||||
|
long long conf_aux_count = 0;
|
||||||
|
|
||||||
|
/* Registered type */
|
||||||
|
RedisModuleType *testrdb_type = NULL;
|
||||||
|
|
||||||
|
/* Global values to store and persist to aux */
|
||||||
|
RedisModuleString *before_str = NULL;
|
||||||
|
RedisModuleString *after_str = NULL;
|
||||||
|
|
||||||
|
void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
|
||||||
|
int count = RedisModule_LoadSigned(rdb);
|
||||||
|
assert(count==1);
|
||||||
|
assert(encver==1);
|
||||||
|
RedisModuleString *str = RedisModule_LoadString(rdb);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testrdb_type_save(RedisModuleIO *rdb, void *value) {
|
||||||
|
RedisModuleString *str = (RedisModuleString*)value;
|
||||||
|
RedisModule_SaveSigned(rdb, 1);
|
||||||
|
RedisModule_SaveString(rdb, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testrdb_aux_save(RedisModuleIO *rdb, int when) {
|
||||||
|
if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
|
||||||
|
if (conf_aux_count==0) assert(0);
|
||||||
|
if (when == REDISMODULE_AUX_BEFORE_RDB) {
|
||||||
|
if (before_str) {
|
||||||
|
RedisModule_SaveSigned(rdb, 1);
|
||||||
|
RedisModule_SaveString(rdb, before_str);
|
||||||
|
} else {
|
||||||
|
RedisModule_SaveSigned(rdb, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (after_str) {
|
||||||
|
RedisModule_SaveSigned(rdb, 1);
|
||||||
|
RedisModule_SaveString(rdb, after_str);
|
||||||
|
} else {
|
||||||
|
RedisModule_SaveSigned(rdb, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
|
||||||
|
assert(encver == 1);
|
||||||
|
if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
|
||||||
|
if (conf_aux_count==0) assert(0);
|
||||||
|
RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
|
||||||
|
if (when == REDISMODULE_AUX_BEFORE_RDB) {
|
||||||
|
if (before_str)
|
||||||
|
RedisModule_FreeString(ctx, before_str);
|
||||||
|
before_str = NULL;
|
||||||
|
int count = RedisModule_LoadSigned(rdb);
|
||||||
|
if (count)
|
||||||
|
before_str = RedisModule_LoadString(rdb);
|
||||||
|
} else {
|
||||||
|
if (after_str)
|
||||||
|
RedisModule_FreeString(ctx, after_str);
|
||||||
|
after_str = NULL;
|
||||||
|
int count = RedisModule_LoadSigned(rdb);
|
||||||
|
if (count)
|
||||||
|
after_str = RedisModule_LoadString(rdb);
|
||||||
|
}
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testrdb_type_free(void *value) {
|
||||||
|
RedisModule_FreeString(NULL, (RedisModuleString*)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int testrdb_set_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (before_str)
|
||||||
|
RedisModule_FreeString(ctx, before_str);
|
||||||
|
before_str = argv[1];
|
||||||
|
RedisModule_RetainString(ctx, argv[1]);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int testrdb_get_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
if (argc != 1){
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
if (before_str)
|
||||||
|
RedisModule_ReplyWithString(ctx, before_str);
|
||||||
|
else
|
||||||
|
RedisModule_ReplyWithStringBuffer(ctx, "", 0);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int testrdb_set_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc != 2){
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (after_str)
|
||||||
|
RedisModule_FreeString(ctx, after_str);
|
||||||
|
after_str = argv[1];
|
||||||
|
RedisModule_RetainString(ctx, argv[1]);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int testrdb_get_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
if (argc != 1){
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
if (after_str)
|
||||||
|
RedisModule_ReplyWithString(ctx, after_str);
|
||||||
|
else
|
||||||
|
RedisModule_ReplyWithStringBuffer(ctx, "", 0);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int testrdb_set_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc != 3){
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||||
|
RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
|
||||||
|
if (str)
|
||||||
|
RedisModule_FreeString(ctx, str);
|
||||||
|
RedisModule_ModuleTypeSetValue(key, testrdb_type, argv[2]);
|
||||||
|
RedisModule_RetainString(ctx, argv[2]);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int testrdb_get_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc != 2){
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||||
|
RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
RedisModule_ReplyWithString(ctx, str);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
|
if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (argc > 0)
|
||||||
|
RedisModule_StringToLongLong(argv[0], &conf_aux_count);
|
||||||
|
|
||||||
|
if (conf_aux_count==0) {
|
||||||
|
RedisModuleTypeMethods datatype_methods = {
|
||||||
|
.version = 1,
|
||||||
|
.rdb_load = testrdb_type_load,
|
||||||
|
.rdb_save = testrdb_type_save,
|
||||||
|
.aof_rewrite = NULL,
|
||||||
|
.digest = NULL,
|
||||||
|
.free = testrdb_type_free,
|
||||||
|
};
|
||||||
|
|
||||||
|
testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
||||||
|
if (testrdb_type == NULL)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
} else {
|
||||||
|
RedisModuleTypeMethods datatype_methods = {
|
||||||
|
.version = REDISMODULE_TYPE_METHOD_VERSION,
|
||||||
|
.rdb_load = testrdb_type_load,
|
||||||
|
.rdb_save = testrdb_type_save,
|
||||||
|
.aof_rewrite = NULL,
|
||||||
|
.digest = NULL,
|
||||||
|
.free = testrdb_type_free,
|
||||||
|
.aux_load = testrdb_aux_load,
|
||||||
|
.aux_save = testrdb_aux_save,
|
||||||
|
.aux_save_triggers = (conf_aux_count == 1 ?
|
||||||
|
REDISMODULE_AUX_AFTER_RDB :
|
||||||
|
REDISMODULE_AUX_BEFORE_RDB | REDISMODULE_AUX_AFTER_RDB)
|
||||||
|
};
|
||||||
|
|
||||||
|
testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
|
||||||
|
if (testrdb_type == NULL)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"testrdb.set.before", testrdb_set_before,"deny-oom",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"testrdb.get.before", testrdb_get_before,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"testrdb.set.after", testrdb_set_after,"deny-oom",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"testrdb.get.after", testrdb_get_after,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"testrdb.set.key", testrdb_set_key,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"testrdb.get.key", testrdb_get_key,"",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
62
tests/unit/moduleapi/testrdb.tcl
Normal file
62
tests/unit/moduleapi/testrdb.tcl
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
set testmodule [file normalize tests/modules/testrdb.so]
|
||||||
|
|
||||||
|
proc restart_and_wait {} {
|
||||||
|
catch {
|
||||||
|
r debug restart
|
||||||
|
}
|
||||||
|
|
||||||
|
# wait for the server to come back up
|
||||||
|
set retry 50
|
||||||
|
while {$retry} {
|
||||||
|
if {[catch { r ping }]} {
|
||||||
|
after 100
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
incr retry -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags "modules" {
|
||||||
|
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||||
|
test {modules are able to persist types} {
|
||||||
|
r testrdb.set.key key1 value1
|
||||||
|
assert_equal "value1" [r testrdb.get.key key1]
|
||||||
|
r debug reload
|
||||||
|
assert_equal "value1" [r testrdb.get.key key1]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {modules global are lost without aux} {
|
||||||
|
r testrdb.set.before global1
|
||||||
|
assert_equal "global1" [r testrdb.get.before]
|
||||||
|
restart_and_wait
|
||||||
|
assert_equal "" [r testrdb.get.before]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server [list overrides [list loadmodule "$testmodule 2"]] {
|
||||||
|
test {modules are able to persist globals before and after} {
|
||||||
|
r testrdb.set.before global1
|
||||||
|
r testrdb.set.after global2
|
||||||
|
assert_equal "global1" [r testrdb.get.before]
|
||||||
|
assert_equal "global2" [r testrdb.get.after]
|
||||||
|
restart_and_wait
|
||||||
|
assert_equal "global1" [r testrdb.get.before]
|
||||||
|
assert_equal "global2" [r testrdb.get.after]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server [list overrides [list loadmodule "$testmodule 1"]] {
|
||||||
|
test {modules are able to persist globals just after} {
|
||||||
|
r testrdb.set.after global2
|
||||||
|
assert_equal "global2" [r testrdb.get.after]
|
||||||
|
restart_and_wait
|
||||||
|
assert_equal "global2" [r testrdb.get.after]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: test short read handling
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user