Merge github.com:antirez/redis into unstable
This commit is contained in:
commit
dd038a522f
53
MANIFESTO
53
MANIFESTO
@ -34,7 +34,21 @@ Redis Manifesto
|
||||
so that the complexity is obvious and more complex operations can be
|
||||
performed as the sum of the basic operations.
|
||||
|
||||
4 - Code is like a poem; it's not just something we write to reach some
|
||||
4 - We believe in code efficiency. Computers get faster and faster, yet we
|
||||
believe that abusing computing capabilities is not wise: the amount of
|
||||
operations you can do for a given amount of energy remains anyway a
|
||||
significant parameter: it allows to do more with less computers and, at
|
||||
the same time, having a smaller environmental impact. Similarly Redis is
|
||||
able to "scale down" to smaller devices. It is perfectly usable in a
|
||||
Raspberry Pi and other small ARM based computers. Faster code having
|
||||
just the layers of abstractions that are really needed will also result,
|
||||
often, in more predictable performances. We think likewise about memory
|
||||
usage, one of the fundamental goals of the Redis project is to
|
||||
incrementally build more and more memory efficient data structures, so that
|
||||
problems that were not approachable in RAM in the past will be perfectly
|
||||
fine to handle in the future.
|
||||
|
||||
5 - Code is like a poem; it's not just something we write to reach some
|
||||
practical result. Sometimes people that are far from the Redis philosophy
|
||||
suggest using other code written by other authors (frequently in other
|
||||
languages) in order to implement something Redis currently lacks. But to us
|
||||
@ -45,23 +59,48 @@ Redis Manifesto
|
||||
when needed. At the same time, when writing the Redis story we're trying to
|
||||
write smaller stories that will fit in to other code.
|
||||
|
||||
5 - We're against complexity. We believe designing systems is a fight against
|
||||
6 - We're against complexity. We believe designing systems is a fight against
|
||||
complexity. We'll accept to fight the complexity when it's worthwhile but
|
||||
we'll try hard to recognize when a small feature is not worth 1000s of lines
|
||||
of code. Most of the time the best way to fight complexity is by not
|
||||
creating it at all.
|
||||
creating it at all. Complexity is also a form of lock-in: code that is
|
||||
very hard to understand cannot be modified by users in an independent way
|
||||
regardless of the license. One of the main Redis goals is to remain
|
||||
understandable, enough for a single programmer to have a clear idea of how
|
||||
it works in detail just reading the source code for a couple of weeks.
|
||||
|
||||
6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
7 - Threading is not a silver bullet. Instead of making Redis threaded we
|
||||
believe on the idea of an efficient (mostly) single threaded Redis core.
|
||||
Multiple of such cores, that may run in the same computer or may run
|
||||
in multiple computers, are abstracted away as a single big system by
|
||||
higher order protocols and features: Redis Cluster and the upcoming
|
||||
Redis Proxy are our main goals. A shared nothing approach is not just
|
||||
much simpler (see the previous point in this document), is also optimal
|
||||
in NUMA systems. In the specific case of Redis it allows for each instance
|
||||
to have a more limited amount of data, making the Redis persist-by-fork
|
||||
approach more sounding. In the future we may explore parallelism only for
|
||||
I/O, which is the low hanging fruit: minimal complexity could provide an
|
||||
improved single process experience.
|
||||
|
||||
8 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
|
||||
naturally into a distributed version of Redis and 2) a more complex API that
|
||||
supports multi-key operations. Both are useful if used judiciously but
|
||||
there's no way to make the more complex multi-keys API distributed in an
|
||||
opaque way without violating our other principles. We don't want to provide
|
||||
the illusion of something that will work magically when actually it can't in
|
||||
all cases. Instead we'll provide commands to quickly migrate keys from one
|
||||
instance to another to perform multi-key operations and expose the tradeoffs
|
||||
to the user.
|
||||
instance to another to perform multi-key operations and expose the
|
||||
trade-offs to the user.
|
||||
|
||||
7 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
9 - We optimize for joy. We believe writing code is a lot of hard work, and the
|
||||
only way it can be worth is by enjoying it. When there is no longer joy in
|
||||
writing code, the best thing to do is stop. To prevent this, we'll avoid
|
||||
taking paths that will make Redis less of a joy to develop.
|
||||
|
||||
10 - All the above points are put together in what we call opportunistic
|
||||
programming: trying to get the most for the user with minimal increases
|
||||
in complexity (hanging fruits). Solve 95% of the problem with 5% of the
|
||||
code when it is acceptable. Avoid a fixed schedule but follow the flow of
|
||||
user requests, inspiration, Redis internal readiness for certain features
|
||||
(sometimes many past changes reach a critical point making a previously
|
||||
complex feature very easy to obtain).
|
||||
|
16
runtest-moduleapi
Executable file
16
runtest-moduleapi
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis test"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
make -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter "${@}"
|
@ -542,6 +542,8 @@ struct redisCommand *ACLLookupCommand(const char *name) {
|
||||
* and command ID. */
|
||||
void ACLResetSubcommandsForCommand(user *u, unsigned long id) {
|
||||
if (u->allowed_subcommands && u->allowed_subcommands[id]) {
|
||||
for (int i = 0; u->allowed_subcommands[id][i]; i++)
|
||||
sdsfree(u->allowed_subcommands[id][i]);
|
||||
zfree(u->allowed_subcommands[id]);
|
||||
u->allowed_subcommands[id] = NULL;
|
||||
}
|
||||
|
@ -1239,7 +1239,7 @@ int rewriteModuleObject(rio *r, robj *key, robj *o) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,r);
|
||||
moduleInitIOContext(io,mt,r,key);
|
||||
mt->aof_rewrite(&io,key,mv->value);
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
|
@ -4776,7 +4776,7 @@ NULL
|
||||
|
||||
/* Generates a DUMP-format representation of the object 'o', adding it to the
|
||||
* io stream pointed by 'rio'. This function can't fail. */
|
||||
void createDumpPayload(rio *payload, robj *o) {
|
||||
void createDumpPayload(rio *payload, robj *o, robj *key) {
|
||||
unsigned char buf[2];
|
||||
uint64_t crc;
|
||||
|
||||
@ -4784,7 +4784,7 @@ void createDumpPayload(rio *payload, robj *o) {
|
||||
* byte followed by the serialized object. This is understood by RESTORE. */
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
serverAssert(rdbSaveObjectType(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o,key));
|
||||
|
||||
/* Write the footer, this is how it looks like:
|
||||
* ----------------+---------------------+---------------+
|
||||
@ -4842,7 +4842,7 @@ void dumpCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Create the DUMP encoded representation. */
|
||||
createDumpPayload(&payload,o);
|
||||
createDumpPayload(&payload,o,c->argv[1]);
|
||||
|
||||
/* Transfer to the client */
|
||||
dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
|
||||
@ -4915,7 +4915,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload)) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@ -5203,7 +5203,7 @@ try_again:
|
||||
|
||||
/* Emit the payload argument, that is the serialized object using
|
||||
* the DUMP format. */
|
||||
createDumpPayload(&payload,ov[j]);
|
||||
createDumpPayload(&payload,ov[j],kv[j]);
|
||||
serverAssertWithInfo(c,NULL,
|
||||
rioWriteBulkString(&cmd,payload.io.buffer.ptr,
|
||||
sdslen(payload.io.buffer.ptr)));
|
||||
|
@ -1074,8 +1074,8 @@ void configSetCommand(client *c) {
|
||||
int soft_seconds;
|
||||
|
||||
class = getClientTypeByName(v[j]);
|
||||
hard = strtoll(v[j+1],NULL,10);
|
||||
soft = strtoll(v[j+2],NULL,10);
|
||||
hard = memtoll(v[j+1],NULL);
|
||||
soft = memtoll(v[j+2],NULL);
|
||||
soft_seconds = strtoll(v[j+3],NULL,10);
|
||||
|
||||
server.client_obuf_limits[class].hard_limit_bytes = hard;
|
||||
|
7
src/db.c
7
src/db.c
@ -83,6 +83,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
* 1. A key gets expired if it reached it's TTL.
|
||||
* 2. The key last access time is updated.
|
||||
* 3. The global keys hits/misses stats are updated (reported in INFO).
|
||||
* 4. If keyspace notifications are enabled, a "keymiss" notification is fired.
|
||||
*
|
||||
* This API should not be used when we write to the key after obtaining
|
||||
* the object linked to the key, but only for read only operations.
|
||||
@ -106,6 +107,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
* to return NULL ASAP. */
|
||||
if (server.masterhost == NULL) {
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -127,12 +129,15 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
server.current_client->cmd->flags & CMD_READONLY)
|
||||
{
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
val = lookupKey(db,key,flags);
|
||||
if (val == NULL)
|
||||
if (val == NULL) {
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
}
|
||||
else
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
|
@ -659,7 +659,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
setKey(c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += returned_items;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
|
@ -614,6 +614,7 @@ int hllSparseToDense(robj *o) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
|
||||
idx++;
|
||||
@ -1013,7 +1014,12 @@ uint64_t hllCount(struct hllhdr *hdr, int *invalid) {
|
||||
double m = HLL_REGISTERS;
|
||||
double E;
|
||||
int j;
|
||||
int reghisto[HLL_Q+2] = {0};
|
||||
/* Note that reghisto size could be just HLL_Q+2, becuase HLL_Q+1 is
|
||||
* the maximum frequency of the "000...1" sequence the hash function is
|
||||
* able to return. However it is slow to check for sanity of the
|
||||
* input: instead we history array at a safe size: overflows will
|
||||
* just write data to wrong, but correctly allocated, places. */
|
||||
int reghisto[64] = {0};
|
||||
|
||||
/* Compute register histogram */
|
||||
if (hdr->encoding == HLL_DENSE) {
|
||||
@ -1088,6 +1094,7 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
|
||||
while(runlen--) {
|
||||
if (regval > max[i]) max[i] = regval;
|
||||
i++;
|
||||
|
452
src/module.c
452
src/module.c
@ -47,9 +47,23 @@ struct RedisModule {
|
||||
int ver; /* Module version. We use just progressive integers. */
|
||||
int apiver; /* Module API version as requested during initialization.*/
|
||||
list *types; /* Module data types. */
|
||||
list *usedby; /* List of modules using APIs from this one. */
|
||||
list *using; /* List of modules we use some APIs of. */
|
||||
list *filters; /* List of filters the module has registered. */
|
||||
int in_call; /* RM_Call() nesting level */
|
||||
};
|
||||
typedef struct RedisModule RedisModule;
|
||||
|
||||
/* This represents a shared API. Shared APIs will be used to populate
|
||||
* the server.sharedapi dictionary, mapping names of APIs exported by
|
||||
* modules for other modules to use, to their structure specifying the
|
||||
* function pointer that can be called. */
|
||||
struct RedisModuleSharedAPI {
|
||||
void *func;
|
||||
RedisModule *module;
|
||||
};
|
||||
typedef struct RedisModuleSharedAPI RedisModuleSharedAPI;
|
||||
|
||||
static dict *modules; /* Hash table of modules. SDS -> RedisModule ptr.*/
|
||||
|
||||
/* Entries in the context->amqueue array, representing objects to free
|
||||
@ -258,6 +272,25 @@ typedef struct RedisModuleDictIter {
|
||||
raxIterator ri;
|
||||
} RedisModuleDictIter;
|
||||
|
||||
typedef struct RedisModuleCommandFilterCtx {
|
||||
RedisModuleString **argv;
|
||||
int argc;
|
||||
} RedisModuleCommandFilterCtx;
|
||||
|
||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||
|
||||
typedef struct RedisModuleCommandFilter {
|
||||
/* The module that registered the filter */
|
||||
RedisModule *module;
|
||||
/* Filter callback function */
|
||||
RedisModuleCommandFilterFunc callback;
|
||||
/* REDISMODULE_CMDFILTER_* flags */
|
||||
int flags;
|
||||
} RedisModuleCommandFilter;
|
||||
|
||||
/* Registered filters */
|
||||
static list *moduleCommandFilters;
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -509,6 +542,22 @@ void RedisModuleCommandDispatcher(client *c) {
|
||||
cp->func(&ctx,(void**)c->argv,c->argc);
|
||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
||||
moduleFreeContext(&ctx);
|
||||
|
||||
/* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
|
||||
* expand the query buffer, and in order to avoid a big object copy
|
||||
* the query buffer SDS may be used directly as the SDS string backing
|
||||
* the client argument vectors: sometimes this will result in the SDS
|
||||
* string having unused space at the end. Later if a module takes ownership
|
||||
* of the RedisString, such space will be wasted forever. Inside the
|
||||
* Redis core this is not a problem because tryObjectEncoding() is called
|
||||
* before storing strings in the key space. Here we need to do it
|
||||
* for the module. */
|
||||
for (int i = 0; i < c->argc; i++) {
|
||||
/* Only do the work if the module took ownership of the object:
|
||||
* in that case the refcount is no longer 1. */
|
||||
if (c->argv[i]->refcount > 1)
|
||||
trimStringObjectIfNeeded(c->argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function returns the list of keys, with the same interface as the
|
||||
@ -701,6 +750,10 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
|
||||
module->ver = ver;
|
||||
module->apiver = apiver;
|
||||
module->types = listCreate();
|
||||
module->usedby = listCreate();
|
||||
module->using = listCreate();
|
||||
module->filters = listCreate();
|
||||
module->in_call = 0;
|
||||
ctx->module = module;
|
||||
}
|
||||
|
||||
@ -2694,12 +2747,6 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
RedisModuleCallReply *reply = NULL;
|
||||
int replicate = 0; /* Replicate this command? */
|
||||
|
||||
cmd = lookupCommandByCString((char*)cmdname);
|
||||
if (!cmd) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create the client and dispatch the command. */
|
||||
va_start(ap, fmt);
|
||||
c = createClient(-1);
|
||||
@ -2713,11 +2760,25 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
c->db = ctx->client->db;
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
c->cmd = c->lastcmd = cmd;
|
||||
if (ctx->module) ctx->module->in_call++;
|
||||
|
||||
/* We handle the above format error only when the client is setup so that
|
||||
* we can free it normally. */
|
||||
if (argv == NULL) goto cleanup;
|
||||
|
||||
/* Call command filters */
|
||||
moduleCallCommandFilters(c);
|
||||
|
||||
/* Lookup command now, after filters had a chance to make modifications
|
||||
* if necessary.
|
||||
*/
|
||||
cmd = lookupCommand(c->argv[0]->ptr);
|
||||
if (!cmd) {
|
||||
errno = EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
c->cmd = c->lastcmd = cmd;
|
||||
|
||||
/* Basic arity checks. */
|
||||
if ((cmd->arity > 0 && cmd->arity != argc) || (argc < -cmd->arity)) {
|
||||
errno = EINVAL;
|
||||
@ -2767,6 +2828,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
autoMemoryAdd(ctx,REDISMODULE_AM_REPLY,reply);
|
||||
|
||||
cleanup:
|
||||
if (ctx->module) ctx->module->in_call--;
|
||||
freeClient(c);
|
||||
return reply;
|
||||
}
|
||||
@ -3408,6 +3470,14 @@ RedisModuleCtx *RM_GetContextFromIO(RedisModuleIO *io) {
|
||||
return io->ctx;
|
||||
}
|
||||
|
||||
/* Returns a RedisModuleString with the name of the key currently saving or
|
||||
* loading, when an IO data type callback is called. There is no guarantee
|
||||
* that the key name is always available, so this may return NULL.
|
||||
*/
|
||||
const RedisModuleString *RM_GetKeyNameFromIO(RedisModuleIO *io) {
|
||||
return io->key;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Logging
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -3429,6 +3499,8 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
|
||||
else if (!strcasecmp(levelstr,"warning")) level = LL_WARNING;
|
||||
else level = LL_VERBOSE; /* Default. */
|
||||
|
||||
if (level < server.verbosity) return;
|
||||
|
||||
name_len = snprintf(msg, sizeof(msg),"<%s> ", module->name);
|
||||
vsnprintf(msg + name_len, sizeof(msg) - name_len, fmt, ap);
|
||||
serverLogRaw(level,msg);
|
||||
@ -3675,14 +3747,7 @@ void moduleHandleBlockedClients(void) {
|
||||
* replies to send to the client in a thread safe context.
|
||||
* We need to glue such replies to the client output buffer and
|
||||
* free the temporary client we just used for the replies. */
|
||||
if (c) {
|
||||
if (bc->reply_client->bufpos)
|
||||
addReplyProto(c,bc->reply_client->buf,
|
||||
bc->reply_client->bufpos);
|
||||
if (listLength(bc->reply_client->reply))
|
||||
listJoin(c->reply,bc->reply_client->reply);
|
||||
c->reply_bytes += bc->reply_client->reply_bytes;
|
||||
}
|
||||
if (c) AddReplyFromClient(c, bc->reply_client);
|
||||
freeClient(bc->reply_client);
|
||||
|
||||
if (c != NULL) {
|
||||
@ -4623,6 +4688,329 @@ void RM_GetRandomHexChars(char *dst, size_t len) {
|
||||
getRandomHexChars(dst,len);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules API exporting / importing
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* This function is called by a module in order to export some API with a
|
||||
* given name. Other modules will be able to use this API by calling the
|
||||
* symmetrical function RM_GetSharedAPI() and casting the return value to
|
||||
* the right function pointer.
|
||||
*
|
||||
* The function will return REDISMODULE_OK if the name is not already taken,
|
||||
* otherwise REDISMODULE_ERR will be returned and no operation will be
|
||||
* performed.
|
||||
*
|
||||
* IMPORTANT: the apiname argument should be a string literal with static
|
||||
* lifetime. The API relies on the fact that it will always be valid in
|
||||
* the future. */
|
||||
int RM_ExportSharedAPI(RedisModuleCtx *ctx, const char *apiname, void *func) {
|
||||
RedisModuleSharedAPI *sapi = zmalloc(sizeof(*sapi));
|
||||
sapi->module = ctx->module;
|
||||
sapi->func = func;
|
||||
if (dictAdd(server.sharedapi, (char*)apiname, sapi) != DICT_OK) {
|
||||
zfree(sapi);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Request an exported API pointer. The return value is just a void pointer
|
||||
* that the caller of this function will be required to cast to the right
|
||||
* function pointer, so this is a private contract between modules.
|
||||
*
|
||||
* If the requested API is not available then NULL is returned. Because
|
||||
* modules can be loaded at different times with different order, this
|
||||
* function calls should be put inside some module generic API registering
|
||||
* step, that is called every time a module attempts to execute a
|
||||
* command that requires external APIs: if some API cannot be resolved, the
|
||||
* command should return an error.
|
||||
*
|
||||
* Here is an exmaple:
|
||||
*
|
||||
* int ... myCommandImplementation() {
|
||||
* if (getExternalAPIs() == 0) {
|
||||
* reply with an error here if we cannot have the APIs
|
||||
* }
|
||||
* // Use the API:
|
||||
* myFunctionPointer(foo);
|
||||
* }
|
||||
*
|
||||
* And the function registerAPI() is:
|
||||
*
|
||||
* int getExternalAPIs(void) {
|
||||
* static int api_loaded = 0;
|
||||
* if (api_loaded != 0) return 1; // APIs already resolved.
|
||||
*
|
||||
* myFunctionPointer = RedisModule_GetOtherModuleAPI("...");
|
||||
* if (myFunctionPointer == NULL) return 0;
|
||||
*
|
||||
* return 1;
|
||||
* }
|
||||
*/
|
||||
void *RM_GetSharedAPI(RedisModuleCtx *ctx, const char *apiname) {
|
||||
dictEntry *de = dictFind(server.sharedapi, apiname);
|
||||
if (de == NULL) return NULL;
|
||||
RedisModuleSharedAPI *sapi = dictGetVal(de);
|
||||
if (listSearchKey(sapi->module->usedby,ctx->module) == NULL) {
|
||||
listAddNodeTail(sapi->module->usedby,ctx->module);
|
||||
listAddNodeTail(ctx->module->using,sapi->module);
|
||||
}
|
||||
return sapi->func;
|
||||
}
|
||||
|
||||
/* Remove all the APIs registered by the specified module. Usually you
|
||||
* want this when the module is going to be unloaded. This function
|
||||
* assumes that's caller responsibility to make sure the APIs are not
|
||||
* used by other modules.
|
||||
*
|
||||
* The number of unregistered APIs is returned. */
|
||||
int moduleUnregisterSharedAPI(RedisModule *module) {
|
||||
int count = 0;
|
||||
dictIterator *di = dictGetSafeIterator(server.sharedapi);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
const char *apiname = dictGetKey(de);
|
||||
RedisModuleSharedAPI *sapi = dictGetVal(de);
|
||||
if (sapi->module == module) {
|
||||
dictDelete(server.sharedapi,apiname);
|
||||
zfree(sapi);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Remove the specified module as an user of APIs of ever other module.
|
||||
* This is usually called when a module is unloaded.
|
||||
*
|
||||
* Returns the number of modules this module was using APIs from. */
|
||||
int moduleUnregisterUsedAPI(RedisModule *module) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
int count = 0;
|
||||
|
||||
listRewind(module->using,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModule *used = ln->value;
|
||||
listNode *ln = listSearchKey(used->usedby,module);
|
||||
if (ln) {
|
||||
listDelNode(module->using,ln);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Unregister all filters registered by a module.
|
||||
* This is called when a module is being unloaded.
|
||||
*
|
||||
* Returns the number of filters unregistered. */
|
||||
int moduleUnregisterFilters(RedisModule *module) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
int count = 0;
|
||||
|
||||
listRewind(module->filters,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModuleCommandFilter *filter = ln->value;
|
||||
listNode *ln = listSearchKey(moduleCommandFilters,filter);
|
||||
if (ln) {
|
||||
listDelNode(moduleCommandFilters,ln);
|
||||
count++;
|
||||
}
|
||||
zfree(filter);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Module Command Filter API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Register a new command filter function.
|
||||
*
|
||||
* Command filtering makes it possible for modules to extend Redis by plugging
|
||||
* into the execution flow of all commands.
|
||||
*
|
||||
* A registered filter gets called before Redis executes *any* command. This
|
||||
* includes both core Redis commands and commands registered by any module. The
|
||||
* filter applies in all execution paths including:
|
||||
*
|
||||
* 1. Invocation by a client.
|
||||
* 2. Invocation through `RedisModule_Call()` by any module.
|
||||
* 3. Invocation through Lua 'redis.call()`.
|
||||
* 4. Replication of a command from a master.
|
||||
*
|
||||
* The filter executes in a special filter context, which is different and more
|
||||
* limited than a RedisModuleCtx. Because the filter affects any command, it
|
||||
* must be implemented in a very efficient way to reduce the performance impact
|
||||
* on Redis. All Redis Module API calls that require a valid context (such as
|
||||
* `RedisModule_Call()`, `RedisModule_OpenKey()`, etc.) are not supported in a
|
||||
* filter context.
|
||||
*
|
||||
* The `RedisModuleCommandFilterCtx` can be used to inspect or modify the
|
||||
* executed command and its arguments. As the filter executes before Redis
|
||||
* begins processing the command, any change will affect the way the command is
|
||||
* processed. For example, a module can override Redis commands this way:
|
||||
*
|
||||
* 1. Register a `MODULE.SET` command which implements an extended version of
|
||||
* the Redis `SET` command.
|
||||
* 2. Register a command filter which detects invocation of `SET` on a specific
|
||||
* pattern of keys. Once detected, the filter will replace the first
|
||||
* argument from `SET` to `MODULE.SET`.
|
||||
* 3. When filter execution is complete, Redis considers the new command name
|
||||
* and therefore executes the module's own command.
|
||||
*
|
||||
* Note that in the above use case, if `MODULE.SET` itself uses
|
||||
* `RedisModule_Call()` the filter will be applied on that call as well. If
|
||||
* that is not desired, the `REDISMODULE_CMDFILTER_NOSELF` flag can be set when
|
||||
* registering the filter.
|
||||
*
|
||||
* The `REDISMODULE_CMDFILTER_NOSELF` flag prevents execution flows that
|
||||
* originate from the module's own `RM_Call()` from reaching the filter. This
|
||||
* flag is effective for all execution flows, including nested ones, as long as
|
||||
* the execution begins from the module's command context or a thread-safe
|
||||
* context that is associated with a blocking command.
|
||||
*
|
||||
* Detached thread-safe contexts are *not* associated with the module and cannot
|
||||
* be protected by this flag.
|
||||
*
|
||||
* If multiple filters are registered (by the same or different modules), they
|
||||
* are executed in the order of registration.
|
||||
*/
|
||||
|
||||
RedisModuleCommandFilter *RM_RegisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc callback, int flags) {
|
||||
RedisModuleCommandFilter *filter = zmalloc(sizeof(*filter));
|
||||
filter->module = ctx->module;
|
||||
filter->callback = callback;
|
||||
filter->flags = flags;
|
||||
|
||||
listAddNodeTail(moduleCommandFilters, filter);
|
||||
listAddNodeTail(ctx->module->filters, filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
/* Unregister a command filter.
|
||||
*/
|
||||
int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) {
|
||||
listNode *ln;
|
||||
|
||||
/* A module can only remove its own filters */
|
||||
if (filter->module != ctx->module) return REDISMODULE_ERR;
|
||||
|
||||
ln = listSearchKey(moduleCommandFilters,filter);
|
||||
if (!ln) return REDISMODULE_ERR;
|
||||
listDelNode(moduleCommandFilters,ln);
|
||||
|
||||
ln = listSearchKey(ctx->module->filters,filter);
|
||||
if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */
|
||||
listDelNode(ctx->module->filters,ln);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void moduleCallCommandFilters(client *c) {
|
||||
if (listLength(moduleCommandFilters) == 0) return;
|
||||
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(moduleCommandFilters,&li);
|
||||
|
||||
RedisModuleCommandFilterCtx filter = {
|
||||
.argv = c->argv,
|
||||
.argc = c->argc
|
||||
};
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
RedisModuleCommandFilter *f = ln->value;
|
||||
|
||||
/* Skip filter if REDISMODULE_CMDFILTER_NOSELF is set and module is
|
||||
* currently processing a command.
|
||||
*/
|
||||
if ((f->flags & REDISMODULE_CMDFILTER_NOSELF) && f->module->in_call) continue;
|
||||
|
||||
/* Call filter */
|
||||
f->callback(&filter);
|
||||
}
|
||||
|
||||
c->argv = filter.argv;
|
||||
c->argc = filter.argc;
|
||||
}
|
||||
|
||||
/* Return the number of arguments a filtered command has. The number of
|
||||
* arguments include the command itself.
|
||||
*/
|
||||
int RM_CommandFilterArgsCount(RedisModuleCommandFilterCtx *fctx)
|
||||
{
|
||||
return fctx->argc;
|
||||
}
|
||||
|
||||
/* Return the specified command argument. The first argument (position 0) is
|
||||
* the command itself, and the rest are user-provided args.
|
||||
*/
|
||||
const RedisModuleString *RM_CommandFilterArgGet(RedisModuleCommandFilterCtx *fctx, int pos)
|
||||
{
|
||||
if (pos < 0 || pos >= fctx->argc) return NULL;
|
||||
return fctx->argv[pos];
|
||||
}
|
||||
|
||||
/* Modify the filtered command by inserting a new argument at the specified
|
||||
* position. The specified RedisModuleString argument may be used by Redis
|
||||
* after the filter context is destroyed, so it must not be auto-memory
|
||||
* allocated, freed or used elsewhere.
|
||||
*/
|
||||
|
||||
int RM_CommandFilterArgInsert(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (pos < 0 || pos > fctx->argc) return REDISMODULE_ERR;
|
||||
|
||||
fctx->argv = zrealloc(fctx->argv, (fctx->argc+1)*sizeof(RedisModuleString *));
|
||||
for (i = fctx->argc; i > pos; i--) {
|
||||
fctx->argv[i] = fctx->argv[i-1];
|
||||
}
|
||||
fctx->argv[pos] = arg;
|
||||
fctx->argc++;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Modify the filtered command by replacing an existing argument with a new one.
|
||||
* The specified RedisModuleString argument may be used by Redis after the
|
||||
* filter context is destroyed, so it must not be auto-memory allocated, freed
|
||||
* or used elsewhere.
|
||||
*/
|
||||
|
||||
int RM_CommandFilterArgReplace(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg)
|
||||
{
|
||||
if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR;
|
||||
|
||||
decrRefCount(fctx->argv[pos]);
|
||||
fctx->argv[pos] = arg;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Modify the filtered command by deleting an argument at the specified
|
||||
* position.
|
||||
*/
|
||||
int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
|
||||
{
|
||||
int i;
|
||||
if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR;
|
||||
|
||||
decrRefCount(fctx->argv[pos]);
|
||||
for (i = pos; i < fctx->argc-1; i++) {
|
||||
fctx->argv[i] = fctx->argv[i+1];
|
||||
}
|
||||
fctx->argc--;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules API internals
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -4669,6 +5057,9 @@ void moduleInitModulesSystem(void) {
|
||||
moduleFreeContextReusedClient->flags |= CLIENT_MODULE;
|
||||
moduleFreeContextReusedClient->user = NULL; /* root user. */
|
||||
|
||||
/* Set up filter list */
|
||||
moduleCommandFilters = listCreate();
|
||||
|
||||
moduleRegisterCoreAPI();
|
||||
if (pipe(server.module_blocked_pipe) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
@ -4718,6 +5109,7 @@ void moduleLoadFromQueue(void) {
|
||||
|
||||
void moduleFreeModuleStructure(struct RedisModule *module) {
|
||||
listRelease(module->types);
|
||||
listRelease(module->filters);
|
||||
sdsfree(module->name);
|
||||
zfree(module);
|
||||
}
|
||||
@ -4767,6 +5159,8 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
if (onload((void*)&ctx,module_argv,module_argc) == REDISMODULE_ERR) {
|
||||
if (ctx.module) {
|
||||
moduleUnregisterCommands(ctx.module);
|
||||
moduleUnregisterSharedAPI(ctx.module);
|
||||
moduleUnregisterUsedAPI(ctx.module);
|
||||
moduleFreeModuleStructure(ctx.module);
|
||||
}
|
||||
dlclose(handle);
|
||||
@ -4796,14 +5190,18 @@ int moduleUnload(sds name) {
|
||||
if (module == NULL) {
|
||||
errno = ENOENT;
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
if (listLength(module->types)) {
|
||||
} else if (listLength(module->types)) {
|
||||
errno = EBUSY;
|
||||
return REDISMODULE_ERR;
|
||||
} else if (listLength(module->usedby)) {
|
||||
errno = EPERM;
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
moduleUnregisterCommands(module);
|
||||
moduleUnregisterSharedAPI(module);
|
||||
moduleUnregisterUsedAPI(module);
|
||||
moduleUnregisterFilters(module);
|
||||
|
||||
/* Remove any notification subscribers this module might have */
|
||||
moduleUnsubscribeNotifications(module);
|
||||
@ -4884,7 +5282,12 @@ NULL
|
||||
errmsg = "no such module with that name";
|
||||
break;
|
||||
case EBUSY:
|
||||
errmsg = "the module exports one or more module-side data types, can't unload";
|
||||
errmsg = "the module exports one or more module-side data "
|
||||
"types, can't unload";
|
||||
break;
|
||||
case EPERM:
|
||||
errmsg = "the module exports APIs used by other modules. "
|
||||
"Please unload them first and try again";
|
||||
break;
|
||||
default:
|
||||
errmsg = "operation not possible.";
|
||||
@ -4909,6 +5312,7 @@ size_t moduleCount(void) {
|
||||
* file so that's easy to seek it to add new entries. */
|
||||
void moduleRegisterCoreAPI(void) {
|
||||
server.moduleapi = dictCreate(&moduleAPIDictType,NULL);
|
||||
server.sharedapi = dictCreate(&moduleAPIDictType,NULL);
|
||||
REGISTER_API(Alloc);
|
||||
REGISTER_API(Calloc);
|
||||
REGISTER_API(Realloc);
|
||||
@ -5006,6 +5410,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(RetainString);
|
||||
REGISTER_API(StringCompare);
|
||||
REGISTER_API(GetContextFromIO);
|
||||
REGISTER_API(GetKeyNameFromIO);
|
||||
REGISTER_API(BlockClient);
|
||||
REGISTER_API(UnblockClient);
|
||||
REGISTER_API(IsBlockedReplyRequest);
|
||||
@ -5059,4 +5464,13 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(DictPrev);
|
||||
REGISTER_API(DictCompareC);
|
||||
REGISTER_API(DictCompare);
|
||||
REGISTER_API(ExportSharedAPI);
|
||||
REGISTER_API(GetSharedAPI);
|
||||
REGISTER_API(RegisterCommandFilter);
|
||||
REGISTER_API(UnregisterCommandFilter);
|
||||
REGISTER_API(CommandFilterArgsCount);
|
||||
REGISTER_API(CommandFilterArgGet);
|
||||
REGISTER_API(CommandFilterArgInsert);
|
||||
REGISTER_API(CommandFilterArgReplace);
|
||||
REGISTER_API(CommandFilterArgDelete);
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ hellotimer.so: hellotimer.xo
|
||||
hellodict.xo: ../redismodule.h
|
||||
|
||||
hellodict.so: hellodict.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
|
@ -109,9 +109,9 @@ int TestStringPrintf(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc < 3) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
|
||||
"Got %d args. argv[1]: %s, argv[2]: %s",
|
||||
argc,
|
||||
RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
|
||||
"Got %d args. argv[1]: %s, argv[2]: %s",
|
||||
argc,
|
||||
RedisModule_StringPtrLen(argv[1], NULL),
|
||||
RedisModule_StringPtrLen(argv[2], NULL)
|
||||
);
|
||||
@ -133,7 +133,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
|
||||
RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "unlinked"), REDISMODULE_WRITE | REDISMODULE_READ);
|
||||
if (!k) return failTest(ctx, "Could not create key");
|
||||
|
||||
|
||||
if (REDISMODULE_ERR == RedisModule_StringSet(k, RedisModule_CreateStringPrintf(ctx, "Foobar"))) {
|
||||
return failTest(ctx, "Could not set string value");
|
||||
}
|
||||
@ -152,7 +152,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return failTest(ctx, "Could not verify key to be unlinked");
|
||||
}
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
|
||||
|
||||
}
|
||||
|
||||
int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
|
||||
@ -188,6 +188,10 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
|
||||
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
|
||||
|
||||
/* Miss some keys intentionally so we will get a "keymiss" notification. */
|
||||
RedisModule_Call(ctx, "GET", "c", "nosuchkey");
|
||||
RedisModule_Call(ctx, "SMEMBERS", "c", "nosuchkey");
|
||||
|
||||
size_t sz;
|
||||
const char *rep;
|
||||
RedisModuleCallReply *r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo");
|
||||
@ -225,6 +229,16 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
FAIL("Wrong reply for l");
|
||||
}
|
||||
|
||||
r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "nosuchkey");
|
||||
if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
|
||||
FAIL("Wrong or no reply for nosuchkey");
|
||||
} else {
|
||||
rep = RedisModule_CallReplyStringPtr(r, &sz);
|
||||
if (sz != 1 || *rep != '2') {
|
||||
FAIL("Got reply '%.*s'. expected '2'", sz, rep);
|
||||
}
|
||||
}
|
||||
|
||||
RedisModule_Call(ctx, "FLUSHDB", "");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
@ -423,7 +437,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx,"test.ctxflags",
|
||||
TestCtxFlags,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"test.unlink",
|
||||
TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
@ -435,7 +449,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
RedisModule_SubscribeToKeyspaceEvents(ctx,
|
||||
REDISMODULE_NOTIFY_HASH |
|
||||
REDISMODULE_NOTIFY_SET |
|
||||
REDISMODULE_NOTIFY_STRING,
|
||||
REDISMODULE_NOTIFY_STRING |
|
||||
REDISMODULE_NOTIFY_KEY_MISS,
|
||||
NotifyCallback);
|
||||
if (RedisModule_CreateCommand(ctx,"test.notify",
|
||||
TestNotifications,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
|
@ -744,6 +744,19 @@ void addReplySubcommandSyntaxError(client *c) {
|
||||
sdsfree(cmd);
|
||||
}
|
||||
|
||||
/* Append 'src' client output buffers into 'dst' client output buffers.
|
||||
* This function clears the output buffers of 'src' */
|
||||
void AddReplyFromClient(client *dst, client *src) {
|
||||
if (prepareClientToWrite(dst) != C_OK)
|
||||
return;
|
||||
addReplyProto(dst,src->buf, src->bufpos);
|
||||
if (listLength(src->reply))
|
||||
listJoin(dst->reply,src->reply);
|
||||
dst->reply_bytes += src->reply_bytes;
|
||||
src->reply_bytes = 0;
|
||||
src->bufpos = 0;
|
||||
}
|
||||
|
||||
/* Copy 'src' client output buffers into 'dst' client output buffers.
|
||||
* The function takes care of freeing the old output buffers of the
|
||||
* destination client. */
|
||||
@ -911,6 +924,16 @@ void unlinkClient(client *c) {
|
||||
c->client_list_node = NULL;
|
||||
}
|
||||
|
||||
/* In the case of diskless replication the fork is writing to the
|
||||
* sockets and just closing the fd isn't enough, if we don't also
|
||||
* shutdown the socket the fork will continue to write to the slave
|
||||
* and the salve will only find out that it was disconnected when
|
||||
* it will finish reading the rdb. */
|
||||
if ((c->flags & CLIENT_SLAVE) &&
|
||||
(c->replstate == SLAVE_STATE_WAIT_BGSAVE_END)) {
|
||||
shutdown(c->fd, SHUT_RDWR);
|
||||
}
|
||||
|
||||
/* Unregister async I/O handlers and close the socket. */
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
|
@ -55,6 +55,7 @@ int keyspaceEventsStringToFlags(char *classes) {
|
||||
case 'K': flags |= NOTIFY_KEYSPACE; break;
|
||||
case 'E': flags |= NOTIFY_KEYEVENT; break;
|
||||
case 't': flags |= NOTIFY_STREAM; break;
|
||||
case 'm': flags |= NOTIFY_KEY_MISS; break;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
@ -81,6 +82,7 @@ sds keyspaceEventsFlagsToString(int flags) {
|
||||
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
|
||||
if (flags & NOTIFY_KEY_MISS) res = sdscatlen(res,"m",1);
|
||||
}
|
||||
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
@ -100,12 +102,12 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
int len = -1;
|
||||
char buf[24];
|
||||
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
* This bypasses the notifications configuration, but the module engine
|
||||
* will only call event subscribers if the event type matches the types
|
||||
* they are interested in. */
|
||||
moduleNotifyKeyspaceEvent(type, event, key, dbid);
|
||||
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
|
||||
|
36
src/object.c
36
src/object.c
@ -415,6 +415,18 @@ int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Optimize the SDS string inside the string object to require little space,
|
||||
* in case there is more than 10% of free space at the end of the SDS
|
||||
* string. This happens because SDS strings tend to overallocate to avoid
|
||||
* wasting too much time in allocations when appending to the string. */
|
||||
void trimStringObjectIfNeeded(robj *o) {
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(o->ptr) > sdslen(o->ptr)/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to encode a string object in order to save space */
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
long value;
|
||||
@ -484,11 +496,7 @@ robj *tryObjectEncoding(robj *o) {
|
||||
* We do that only for relatively large strings as this branch
|
||||
* is only entered if the length of the string is greater than
|
||||
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
if (o->encoding == OBJ_ENCODING_RAW &&
|
||||
sdsavail(s) > len/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
trimStringObjectIfNeeded(o);
|
||||
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
@ -1191,7 +1199,7 @@ sds getMemoryDoctorReport(void) {
|
||||
|
||||
/* Set the object LRU/LFU depending on server.maxmemory_policy.
|
||||
* The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* is MAXMEMORY_FLAG_LRU.
|
||||
* Either or both of them may be <0, in that case, nothing is set. */
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
@ -1202,16 +1210,20 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||
}
|
||||
} else if (lru_idle >= 0) {
|
||||
/* Serialized LRU idle time is in seconds. Scale
|
||||
/* Provided LRU idle time is in seconds. Scale
|
||||
* according to the LRU clock resolution this Redis
|
||||
* instance was compiled with (normally 1000 ms, so the
|
||||
* below statement will expand to lru_idle*1000/1000. */
|
||||
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
|
||||
val->lru = lru_clock - lru_idle;
|
||||
/* If the lru field overflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide the maximum
|
||||
* representable idle time. */
|
||||
if (val->lru < 0) val->lru = lru_clock+1;
|
||||
long lru_abs = lru_clock - lru_idle; /* Absolute access time. */
|
||||
/* If the LRU field underflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide a large enough LRU
|
||||
* that is half-way in the circlular LRU clock we use: this way
|
||||
* the computed idle time for this object will stay high for quite
|
||||
* some time. */
|
||||
if (lru_abs < 0)
|
||||
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
||||
val->lru = lru_abs;
|
||||
}
|
||||
}
|
||||
|
||||
|
14
src/rdb.c
14
src/rdb.c
@ -751,7 +751,7 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
|
||||
|
||||
/* Save a Redis object.
|
||||
* Returns -1 on error, number of bytes written on success. */
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
ssize_t n = 0, nwritten = 0;
|
||||
|
||||
if (o->type == OBJ_STRING) {
|
||||
@ -966,7 +966,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
|
||||
/* Write the "module" identifier as prefix, so that we'll be able
|
||||
* to call the right module during loading. */
|
||||
@ -996,7 +996,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
size_t rdbSavedObjectLen(robj *o) {
|
||||
ssize_t len = rdbSaveObject(NULL,o);
|
||||
ssize_t len = rdbSaveObject(NULL,o,NULL);
|
||||
serverAssertWithInfo(NULL,o,len != -1);
|
||||
return len;
|
||||
}
|
||||
@ -1038,7 +1038,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
|
||||
/* Save type, key, value */
|
||||
if (rdbSaveObjectType(rdb,val) == -1) return -1;
|
||||
if (rdbSaveStringObject(rdb,key) == -1) return -1;
|
||||
if (rdbSaveObject(rdb,val) == -1) return -1;
|
||||
if (rdbSaveObject(rdb,val,key) == -1) return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1380,7 +1380,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@ -1767,7 +1767,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
exit(1);
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
@ -2023,7 +2023,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
/* Read key */
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. This function is used when loading
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
|
@ -140,9 +140,9 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
robj *rdbLoadStringObject(rio *rdb);
|
||||
|
@ -1197,7 +1197,7 @@ static int fetchClusterSlotsConfiguration(client c) {
|
||||
assert(reply->type == REDIS_REPLY_ARRAY);
|
||||
for (i = 0; i < reply->elements; i++) {
|
||||
redisReply *r = reply->element[i];
|
||||
assert(r->type = REDIS_REPLY_ARRAY);
|
||||
assert(r->type == REDIS_REPLY_ARRAY);
|
||||
assert(r->elements >= 3);
|
||||
int from, to, slot;
|
||||
from = r->element[0]->integer;
|
||||
|
@ -33,8 +33,8 @@
|
||||
|
||||
#define ERROR(...) { \
|
||||
char __buf[1024]; \
|
||||
sprintf(__buf, __VA_ARGS__); \
|
||||
sprintf(error, "0x%16llx: %s", (long long)epos, __buf); \
|
||||
snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
|
||||
snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
|
||||
}
|
||||
|
||||
static char error[1024];
|
||||
|
@ -285,7 +285,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
|
@ -98,7 +98,8 @@
|
||||
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
|
||||
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_KEY_MISS) /* A */
|
||||
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
@ -132,6 +133,11 @@
|
||||
* of timers that are going to expire, sorted by expire time. */
|
||||
typedef uint64_t RedisModuleTimerID;
|
||||
|
||||
/* CommandFilter Flags */
|
||||
|
||||
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
||||
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
|
||||
|
||||
/* ------------------------- End of common defines ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
@ -150,6 +156,8 @@ typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
typedef struct RedisModuleClusterInfo RedisModuleClusterInfo;
|
||||
typedef struct RedisModuleDict RedisModuleDict;
|
||||
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||
@ -162,6 +170,7 @@ typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value
|
||||
typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
||||
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
||||
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 1
|
||||
typedef struct RedisModuleTypeMethods {
|
||||
@ -278,6 +287,7 @@ int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, Re
|
||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
|
||||
@ -335,6 +345,15 @@ void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t
|
||||
void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname);
|
||||
RedisModuleCommandFilter *REDISMODULE_API_FUNC(RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
#endif
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
@ -440,6 +459,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(RetainString);
|
||||
REDISMODULE_GET_API(StringCompare);
|
||||
REDISMODULE_GET_API(GetContextFromIO);
|
||||
REDISMODULE_GET_API(GetKeyNameFromIO);
|
||||
REDISMODULE_GET_API(Milliseconds);
|
||||
REDISMODULE_GET_API(DigestAddStringBuffer);
|
||||
REDISMODULE_GET_API(DigestAddLongLong);
|
||||
@ -495,6 +515,15 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(GetRandomBytes);
|
||||
REDISMODULE_GET_API(GetRandomHexChars);
|
||||
REDISMODULE_GET_API(SetClusterFlags);
|
||||
REDISMODULE_GET_API(ExportSharedAPI);
|
||||
REDISMODULE_GET_API(GetSharedAPI);
|
||||
REDISMODULE_GET_API(RegisterCommandFilter);
|
||||
REDISMODULE_GET_API(UnregisterCommandFilter);
|
||||
REDISMODULE_GET_API(CommandFilterArgsCount);
|
||||
REDISMODULE_GET_API(CommandFilterArgGet);
|
||||
REDISMODULE_GET_API(CommandFilterArgInsert);
|
||||
REDISMODULE_GET_API(CommandFilterArgReplace);
|
||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
|
@ -593,6 +593,7 @@ int startBgsaveForReplication(int mincapa) {
|
||||
client *slave = ln->value;
|
||||
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
|
||||
slave->replstate = REPL_STATE_NONE;
|
||||
slave->flags &= ~CLIENT_SLAVE;
|
||||
listDelNode(server.slaves,ln);
|
||||
addReplyError(slave,
|
||||
@ -1090,14 +1091,23 @@ void replicationCreateMasterClient(int fd, int dbid) {
|
||||
if (dbid != -1) selectDb(server.master,dbid);
|
||||
}
|
||||
|
||||
void restartAOF() {
|
||||
int retry = 10;
|
||||
while (retry-- && startAppendOnly() == C_ERR) {
|
||||
serverLog(LL_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
|
||||
/* This function will try to re-enable the AOF file after the
|
||||
* master-replica synchronization: if it fails after multiple attempts
|
||||
* the replica cannot be considered reliable and exists with an
|
||||
* error. */
|
||||
void restartAOFAfterSYNC() {
|
||||
unsigned int tries, max_tries = 10;
|
||||
for (tries = 0; tries < max_tries; ++tries) {
|
||||
if (startAppendOnly() == C_OK) break;
|
||||
serverLog(LL_WARNING,
|
||||
"Failed enabling the AOF after successful master synchronization! "
|
||||
"Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (!retry) {
|
||||
serverLog(LL_WARNING,"FATAL: this replica instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
|
||||
if (tries == max_tries) {
|
||||
serverLog(LL_WARNING,
|
||||
"FATAL: this replica instance finished the synchronization with "
|
||||
"its master, but the AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@ -1284,7 +1294,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
cancelReplicationHandshake();
|
||||
/* Re-enable the AOF if we disabled it earlier, in order to restore
|
||||
* the original configuration. */
|
||||
if (aof_is_enabled) restartAOF();
|
||||
if (aof_is_enabled) restartAOFAfterSYNC();
|
||||
return;
|
||||
}
|
||||
/* Final setup of the connected slave <- master link */
|
||||
@ -1309,7 +1319,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
* to the new file. */
|
||||
if (aof_is_enabled) restartAOF();
|
||||
if (aof_is_enabled) restartAOFAfterSYNC();
|
||||
}
|
||||
return;
|
||||
|
||||
@ -2053,8 +2063,11 @@ void replicaofCommand(client *c) {
|
||||
/* Check if we are already attached to the specified slave */
|
||||
if (server.masterhost && !strcasecmp(server.masterhost,c->argv[1]->ptr)
|
||||
&& server.masterport == port) {
|
||||
serverLog(LL_NOTICE,"REPLICAOF would result into synchronization with the master we are already connected with. No operation performed.");
|
||||
addReplySds(c,sdsnew("+OK Already connected to specified master\r\n"));
|
||||
serverLog(LL_NOTICE,"REPLICAOF would result into synchronization "
|
||||
"with the master we are already connected "
|
||||
"with. No operation performed.");
|
||||
addReplySds(c,sdsnew("+OK Already connected to specified "
|
||||
"master\r\n"));
|
||||
return;
|
||||
}
|
||||
/* There was no previous master or the user specified a different one,
|
||||
|
@ -462,6 +462,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
c->argc = argc;
|
||||
c->user = server.lua_caller->user;
|
||||
|
||||
/* Process module hooks */
|
||||
moduleCallCommandFilters(c);
|
||||
argv = c->argv;
|
||||
argc = c->argc;
|
||||
|
||||
/* Log the command if debugging is active. */
|
||||
if (ldb.active && ldb.step) {
|
||||
sds cmdlog = sdsnew("<redis>");
|
||||
|
@ -257,8 +257,12 @@ sds sdsRemoveFreeSpace(sds s) {
|
||||
char type, oldtype = s[-1] & SDS_TYPE_MASK;
|
||||
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
|
||||
size_t len = sdslen(s);
|
||||
size_t avail = sdsavail(s);
|
||||
sh = (char*)s-oldhdrlen;
|
||||
|
||||
/* Return ASAP if there is no space left. */
|
||||
if (avail == 0) return s;
|
||||
|
||||
/* Check what would be the minimum SDS header that is just good enough to
|
||||
* fit this string. */
|
||||
type = sdsReqType(len);
|
||||
|
@ -715,7 +715,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
{"touch",touchCommand,-2,
|
||||
"read-only fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
|
||||
{"pttl",pttlCommand,2,
|
||||
"read-only fast random @keyspace",
|
||||
@ -863,7 +863,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
"no-script @keyspace",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"command",commandCommand,0,
|
||||
{"command",commandCommand,-1,
|
||||
"ok-loading ok-stale random @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
@ -3268,6 +3268,8 @@ void call(client *c, int flags) {
|
||||
* other operations can be performed by the caller. Otherwise
|
||||
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
|
||||
int processCommand(client *c) {
|
||||
moduleCallCommandFilters(c);
|
||||
|
||||
/* The QUIT command is handled separately. Normal command procs will
|
||||
* go through checking for replication and QUIT will cause trouble
|
||||
* when FORCE_REPLICATION is enabled and would be implemented in
|
||||
@ -4500,6 +4502,7 @@ static void sigShutdownHandler(int sig) {
|
||||
rdbRemoveTempFile(getpid());
|
||||
exit(1); /* Exit with an error since this was not a clean shutdown. */
|
||||
} else if (server.loading) {
|
||||
serverLogFromHandler(LL_WARNING, "Received shutdown signal during loading, exiting now.");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
15
src/server.h
15
src/server.h
@ -468,7 +468,8 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define NOTIFY_STREAM (1<<10) /* t */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */
|
||||
#define NOTIFY_KEY_MISS (1<<11) /* m */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_KEY_MISS) /* A flag */
|
||||
|
||||
/* Get the first bind addr or NULL */
|
||||
#define NET_FIRST_BIND_ADDR (server.bindaddr_count ? server.bindaddr[0] : NULL)
|
||||
@ -578,16 +579,18 @@ typedef struct RedisModuleIO {
|
||||
int ver; /* Module serialization version: 1 (old),
|
||||
* 2 (current version with opcodes annotation). */
|
||||
struct RedisModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/
|
||||
struct redisObject *key; /* Optional name of key processed */
|
||||
} RedisModuleIO;
|
||||
|
||||
/* Macro to initialize an IO context. Note that the 'ver' field is populated
|
||||
* inside rdb.c according to the version of the value to load. */
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr) do { \
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr,keyptr) do { \
|
||||
iovar.rio = rioptr; \
|
||||
iovar.type = mtype; \
|
||||
iovar.bytes = 0; \
|
||||
iovar.error = 0; \
|
||||
iovar.ver = 0; \
|
||||
iovar.key = keyptr; \
|
||||
iovar.ctx = NULL; \
|
||||
} while(0);
|
||||
|
||||
@ -1026,7 +1029,9 @@ struct redisServer {
|
||||
size_t initial_memory_usage; /* Bytes used after initialization. */
|
||||
int always_show_logo; /* Show logo even for non-stdout logging. */
|
||||
/* Modules */
|
||||
dict *moduleapi; /* Exported APIs dictionary for modules. */
|
||||
dict *moduleapi; /* Exported core APIs dictionary for modules. */
|
||||
dict *sharedapi; /* Like moduleapi but containing the APIs that
|
||||
modules share with each other. */
|
||||
list *loadmodule_queue; /* List of modules to load at startup. */
|
||||
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
||||
client blocked on a module command needs
|
||||
@ -1487,7 +1492,7 @@ size_t moduleCount(void);
|
||||
void moduleAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
|
||||
void moduleCallCommandFilters(client *c);
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
@ -1524,6 +1529,7 @@ void addReplyNullArray(client *c);
|
||||
void addReplyBool(client *c, int b);
|
||||
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
|
||||
void addReplyProto(client *c, const char *s, size_t len);
|
||||
void AddReplyFromClient(client *c, client *src);
|
||||
void addReplyBulk(client *c, robj *obj);
|
||||
void addReplyBulkCString(client *c, const char *s);
|
||||
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
|
||||
@ -1660,6 +1666,7 @@ int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
void trimStringObjectIfNeeded(robj *o);
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
|
55
src/sort.c
55
src/sort.c
@ -58,7 +58,7 @@ redisSortOperation *createSortOperation(int type, robj *pattern) {
|
||||
*
|
||||
* The returned object will always have its refcount increased by 1
|
||||
* when it is non-NULL. */
|
||||
robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
|
||||
robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst, int writeflag) {
|
||||
char *p, *f, *k;
|
||||
sds spat, ssub;
|
||||
robj *keyobj, *fieldobj = NULL, *o;
|
||||
@ -106,7 +106,10 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
|
||||
decrRefCount(subst); /* Incremented by decodeObject() */
|
||||
|
||||
/* Lookup substituted key */
|
||||
o = lookupKeyRead(db,keyobj);
|
||||
if (!writeflag)
|
||||
o = lookupKeyRead(db,keyobj);
|
||||
else
|
||||
o = lookupKeyWrite(db,keyobj);
|
||||
if (o == NULL) goto noobj;
|
||||
|
||||
if (fieldobj) {
|
||||
@ -198,30 +201,12 @@ void sortCommand(client *c) {
|
||||
robj *sortval, *sortby = NULL, *storekey = NULL;
|
||||
redisSortObject *vector; /* Resulting vector to sort */
|
||||
|
||||
/* Lookup the key to sort. It must be of the right types */
|
||||
sortval = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (sortval && sortval->type != OBJ_SET &&
|
||||
sortval->type != OBJ_LIST &&
|
||||
sortval->type != OBJ_ZSET)
|
||||
{
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a list of operations to perform for every sorted element.
|
||||
* Operations can be GET */
|
||||
operations = listCreate();
|
||||
listSetFreeMethod(operations,zfree);
|
||||
j = 2; /* options start at argv[2] */
|
||||
|
||||
/* Now we need to protect sortval incrementing its count, in the future
|
||||
* SORT may have options able to overwrite/delete keys during the sorting
|
||||
* and the sorted key itself may get destroyed */
|
||||
if (sortval)
|
||||
incrRefCount(sortval);
|
||||
else
|
||||
sortval = createQuicklistObject();
|
||||
|
||||
/* The SORT command has an SQL-alike syntax, parse it */
|
||||
while(j < c->argc) {
|
||||
int leftargs = c->argc-j-1;
|
||||
@ -280,11 +265,33 @@ void sortCommand(client *c) {
|
||||
|
||||
/* Handle syntax errors set during options parsing. */
|
||||
if (syntax_error) {
|
||||
decrRefCount(sortval);
|
||||
listRelease(operations);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lookup the key to sort. It must be of the right types */
|
||||
if (storekey)
|
||||
sortval = lookupKeyRead(c->db,c->argv[1]);
|
||||
else
|
||||
sortval = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (sortval && sortval->type != OBJ_SET &&
|
||||
sortval->type != OBJ_LIST &&
|
||||
sortval->type != OBJ_ZSET)
|
||||
{
|
||||
listRelease(operations);
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now we need to protect sortval incrementing its count, in the future
|
||||
* SORT may have options able to overwrite/delete keys during the sorting
|
||||
* and the sorted key itself may get destroyed */
|
||||
if (sortval)
|
||||
incrRefCount(sortval);
|
||||
else
|
||||
sortval = createQuicklistObject();
|
||||
|
||||
|
||||
/* When sorting a set with no sort specified, we must sort the output
|
||||
* so the result is consistent across scripting and replication.
|
||||
*
|
||||
@ -452,7 +459,7 @@ void sortCommand(client *c) {
|
||||
robj *byval;
|
||||
if (sortby) {
|
||||
/* lookup value to sort by */
|
||||
byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
|
||||
byval = lookupKeyByPattern(c->db,sortby,vector[j].obj,storekey!=NULL);
|
||||
if (!byval) continue;
|
||||
} else {
|
||||
/* use object itself to sort by */
|
||||
@ -515,7 +522,7 @@ void sortCommand(client *c) {
|
||||
while((ln = listNext(&li))) {
|
||||
redisSortOperation *sop = ln->value;
|
||||
robj *val = lookupKeyByPattern(c->db,sop->pattern,
|
||||
vector[j].obj);
|
||||
vector[j].obj,storekey!=NULL);
|
||||
|
||||
if (sop->type == SORT_OP_GET) {
|
||||
if (!val) {
|
||||
@ -545,7 +552,7 @@ void sortCommand(client *c) {
|
||||
while((ln = listNext(&li))) {
|
||||
redisSortOperation *sop = ln->value;
|
||||
robj *val = lookupKeyByPattern(c->db,sop->pattern,
|
||||
vector[j].obj);
|
||||
vector[j].obj,storekey!=NULL);
|
||||
|
||||
if (sop->type == SORT_OP_GET) {
|
||||
if (!val) val = createStringObject("",0);
|
||||
|
@ -615,6 +615,10 @@ void hincrbyfloatCommand(client *c) {
|
||||
}
|
||||
|
||||
value += incr;
|
||||
if (isnan(value) || isinf(value)) {
|
||||
addReplyError(c,"increment would produce NaN or Infinity");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf,sizeof(buf),value,1);
|
||||
|
@ -520,7 +520,7 @@ void lremCommand(client *c) {
|
||||
|
||||
if (removed) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"lrem",c->argv[1],c->db->id);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
if (listTypeLength(subject) == 0) {
|
||||
|
@ -415,7 +415,7 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* Make sure a key with the name inputted exists, and that it's type is
|
||||
* indeed a set. Otherwise, return nil */
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
/* If count is zero, serve an empty multibulk ASAP to avoid special
|
||||
|
10
src/t_zset.c
10
src/t_zset.c
@ -2906,7 +2906,10 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
||||
while (remaining) {
|
||||
if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL) != C_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) return;
|
||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) {
|
||||
zslFreeLexRange(&range);
|
||||
return;
|
||||
}
|
||||
pos += 3; remaining -= 3;
|
||||
} else {
|
||||
zslFreeLexRange(&range);
|
||||
@ -3140,7 +3143,10 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
||||
if (countarg) {
|
||||
if (getLongFromObjectOrReply(c,countarg,&count,NULL) != C_OK)
|
||||
return;
|
||||
if (count < 0) count = 1;
|
||||
if (count <= 0) {
|
||||
addReply(c,shared.emptyarray);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check type and break on the first error, otherwise identify candidate. */
|
||||
|
@ -447,7 +447,7 @@ int string2l(const char *s, size_t slen, long *lval) {
|
||||
* a double: no spaces or other characters before or after the string
|
||||
* representing the number are accepted. */
|
||||
int string2ld(const char *s, size_t slen, long double *dp) {
|
||||
char buf[256];
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
long double value;
|
||||
char *eptr;
|
||||
|
||||
|
@ -148,6 +148,10 @@ void *zrealloc(void *ptr, size_t size) {
|
||||
size_t oldsize;
|
||||
void *newptr;
|
||||
|
||||
if (size == 0 && ptr != NULL) {
|
||||
zfree(ptr);
|
||||
return NULL;
|
||||
}
|
||||
if (ptr == NULL) return zmalloc(size);
|
||||
#ifdef HAVE_MALLOC_SIZE
|
||||
oldsize = zmalloc_size(ptr);
|
||||
|
24
tests/modules/Makefile
Normal file
24
tests/modules/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
# find the OS
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
# Compile flags for linux / osx
|
||||
ifeq ($(uname_S),Linux)
|
||||
SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2
|
||||
SHOBJ_LDFLAGS ?= -shared
|
||||
else
|
||||
SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c99 -O2
|
||||
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
|
||||
endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: commandfilter.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
|
||||
commandfilter.xo: ../../src/redismodule.h
|
||||
|
||||
commandfilter.so: commandfilter.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
149
tests/modules/commandfilter.c
Normal file
149
tests/modules/commandfilter.c
Normal file
@ -0,0 +1,149 @@
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "redismodule.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static RedisModuleString *log_key_name;
|
||||
|
||||
static const char log_command_name[] = "commandfilter.log";
|
||||
static const char ping_command_name[] = "commandfilter.ping";
|
||||
static const char unregister_command_name[] = "commandfilter.unregister";
|
||||
static int in_log_command = 0;
|
||||
|
||||
static RedisModuleCommandFilter *filter = NULL;
|
||||
|
||||
int CommandFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx,
|
||||
RedisModule_UnregisterCommandFilter(ctx, filter));
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int CommandFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "c", "@log");
|
||||
if (reply) {
|
||||
RedisModule_ReplyWithCallReply(ctx, reply);
|
||||
RedisModule_FreeCallReply(reply);
|
||||
} else {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int CommandFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
RedisModuleString *s = RedisModule_CreateString(ctx, "", 0);
|
||||
|
||||
int i;
|
||||
for (i = 1; i < argc; i++) {
|
||||
size_t arglen;
|
||||
const char *arg = RedisModule_StringPtrLen(argv[i], &arglen);
|
||||
|
||||
if (i > 1) RedisModule_StringAppendBuffer(ctx, s, " ", 1);
|
||||
RedisModule_StringAppendBuffer(ctx, s, arg, arglen);
|
||||
}
|
||||
|
||||
RedisModuleKey *log = RedisModule_OpenKey(ctx, log_key_name, REDISMODULE_WRITE|REDISMODULE_READ);
|
||||
RedisModule_ListPush(log, REDISMODULE_LIST_HEAD, s);
|
||||
RedisModule_CloseKey(log);
|
||||
RedisModule_FreeString(ctx, s);
|
||||
|
||||
in_log_command = 1;
|
||||
|
||||
size_t cmdlen;
|
||||
const char *cmdname = RedisModule_StringPtrLen(argv[1], &cmdlen);
|
||||
RedisModuleCallReply *reply = RedisModule_Call(ctx, cmdname, "v", &argv[2], argc - 2);
|
||||
if (reply) {
|
||||
RedisModule_ReplyWithCallReply(ctx, reply);
|
||||
RedisModule_FreeCallReply(reply);
|
||||
} else {
|
||||
RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
|
||||
}
|
||||
|
||||
in_log_command = 0;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void CommandFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
|
||||
{
|
||||
if (in_log_command) return; /* don't process our own RM_Call() from CommandFilter_LogCommand() */
|
||||
|
||||
/* Fun manipulations:
|
||||
* - Remove @delme
|
||||
* - Replace @replaceme
|
||||
* - Append @insertbefore or @insertafter
|
||||
* - Prefix with Log command if @log encounterd
|
||||
*/
|
||||
int log = 0;
|
||||
int pos = 0;
|
||||
while (pos < RedisModule_CommandFilterArgsCount(filter)) {
|
||||
const RedisModuleString *arg = RedisModule_CommandFilterArgGet(filter, pos);
|
||||
size_t arg_len;
|
||||
const char *arg_str = RedisModule_StringPtrLen(arg, &arg_len);
|
||||
|
||||
if (arg_len == 6 && !memcmp(arg_str, "@delme", 6)) {
|
||||
RedisModule_CommandFilterArgDelete(filter, pos);
|
||||
continue;
|
||||
}
|
||||
if (arg_len == 10 && !memcmp(arg_str, "@replaceme", 10)) {
|
||||
RedisModule_CommandFilterArgReplace(filter, pos,
|
||||
RedisModule_CreateString(NULL, "--replaced--", 12));
|
||||
} else if (arg_len == 13 && !memcmp(arg_str, "@insertbefore", 13)) {
|
||||
RedisModule_CommandFilterArgInsert(filter, pos,
|
||||
RedisModule_CreateString(NULL, "--inserted-before--", 19));
|
||||
pos++;
|
||||
} else if (arg_len == 12 && !memcmp(arg_str, "@insertafter", 12)) {
|
||||
RedisModule_CommandFilterArgInsert(filter, pos + 1,
|
||||
RedisModule_CreateString(NULL, "--inserted-after--", 18));
|
||||
pos++;
|
||||
} else if (arg_len == 4 && !memcmp(arg_str, "@log", 4)) {
|
||||
log = 1;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (log) RedisModule_CommandFilterArgInsert(filter, 0,
|
||||
RedisModule_CreateString(NULL, log_command_name, sizeof(log_command_name)-1));
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (RedisModule_Init(ctx,"commandfilter",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (argc != 2) {
|
||||
RedisModule_Log(ctx, "warning", "Log key name not specified");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
long long noself = 0;
|
||||
log_key_name = RedisModule_CreateStringFromString(ctx, argv[0]);
|
||||
RedisModule_StringToLongLong(argv[1], &noself);
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,log_command_name,
|
||||
CommandFilter_LogCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,ping_command_name,
|
||||
CommandFilter_PingCommand,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,unregister_command_name,
|
||||
CommandFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if ((filter = RedisModule_RegisterCommandFilter(ctx, CommandFilter_CommandFilter,
|
||||
noself ? REDISMODULE_CMDFILTER_NOSELF : 0))
|
||||
== NULL) return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
@ -108,4 +108,11 @@ start_server {tags {"acl"}} {
|
||||
assert_match {*+debug|segfault*} $cmdstr
|
||||
assert_match {*+acl*} $cmdstr
|
||||
}
|
||||
|
||||
test {ACL #5998 regression: memory leaks adding / removing subcommands} {
|
||||
r AUTH default ""
|
||||
r ACL setuser newuser reset -debug +debug|a +debug|b +debug|c
|
||||
r ACL setuser newuser -debug
|
||||
# The test framework will detect a leak if any.
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,34 @@ start_server {tags {"hll"}} {
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Fuzzing dense/sparse encoding: Redis should always detect errors} {
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
r del hll
|
||||
set items {}
|
||||
set numitems [randomInt 3000]
|
||||
for {set i 0} {$i < $numitems} {incr i} {
|
||||
lappend items [expr {rand()}]
|
||||
}
|
||||
r pfadd hll {*}$items
|
||||
|
||||
# Corrupt it in some random way.
|
||||
for {set i 0} {$i < 5} {incr i} {
|
||||
set len [r strlen hll]
|
||||
set pos [randomInt $len]
|
||||
set byte [randstring 1 1 binary]
|
||||
r setrange hll $pos $byte
|
||||
# Don't modify more bytes 50% of times
|
||||
if {rand() < 0.5} break
|
||||
}
|
||||
|
||||
# Use the hyperloglog to check if it crashes
|
||||
# Redis in some way.
|
||||
catch {
|
||||
r pfcount hll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {PFADD, PFCOUNT, PFMERGE type checking works} {
|
||||
r set foo bar
|
||||
catch {r pfadd foo 1} e
|
||||
|
84
tests/unit/moduleapi/commandfilter.tcl
Normal file
84
tests/unit/moduleapi/commandfilter.tcl
Normal file
@ -0,0 +1,84 @@
|
||||
set testmodule [file normalize tests/modules/commandfilter.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule log-key 0
|
||||
|
||||
test {Command Filter handles redirected commands} {
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} "{set mykey @log}"
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgDelete} {
|
||||
r rpush mylist elem1 @delme elem2
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 elem2}
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgInsert} {
|
||||
r del mylist
|
||||
r rpush mylist elem1 @insertbefore elem2 @insertafter elem3
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3}
|
||||
|
||||
test {Command Filter can call RedisModule_CommandFilterArgReplace} {
|
||||
r del mylist
|
||||
r rpush mylist elem1 @replaceme elem2
|
||||
r lrange mylist 0 -1
|
||||
} {elem1 --replaced-- elem2}
|
||||
|
||||
test {Command Filter applies on RM_Call() commands} {
|
||||
r del log-key
|
||||
r commandfilter.ping
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter applies on Lua redis.call()} {
|
||||
r del log-key
|
||||
r eval "redis.call('ping', '@log')" 0
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter applies on Lua redis.call() that calls a module} {
|
||||
r del log-key
|
||||
r eval "redis.call('commandfilter.ping')" 0
|
||||
r lrange log-key 0 -1
|
||||
} "{ping @log}"
|
||||
|
||||
test {Command Filter is unregistered implicitly on module unload} {
|
||||
r del log-key
|
||||
r module unload commandfilter
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} {}
|
||||
|
||||
r module load $testmodule log-key 0
|
||||
|
||||
test {Command Filter unregister works as expected} {
|
||||
# Validate reloading succeeded
|
||||
r del log-key
|
||||
r set mykey @log
|
||||
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
|
||||
|
||||
# Unregister
|
||||
r commandfilter.unregister
|
||||
r del log-key
|
||||
|
||||
r set mykey @log
|
||||
r lrange log-key 0 -1
|
||||
} {}
|
||||
|
||||
r module unload commandfilter
|
||||
r module load $testmodule log-key 1
|
||||
|
||||
test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} {
|
||||
r set mykey @log
|
||||
assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
|
||||
|
||||
r del log-key
|
||||
r commandfilter.ping
|
||||
assert_equal {} [r lrange log-key 0 -1]
|
||||
|
||||
r eval "redis.call('commandfilter.ping')" 0
|
||||
assert_equal {} [r lrange log-key 0 -1]
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user