Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dde8bf3ab | ||
|
|
34599691b3 | ||
|
|
f156038db8 | ||
|
|
ffb00fbcbe | ||
|
|
3aa816e61a | ||
|
|
3879923db8 | ||
|
|
8fadfe52a2 |
+6
-1
@@ -136,6 +136,8 @@ void unblockClient(client *c) {
|
||||
unblockClientWaitingData(c);
|
||||
} else if (c->btype == BLOCKED_WAIT) {
|
||||
unblockClientWaitingReplicas(c);
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
unblockClientFromModule(c);
|
||||
} else {
|
||||
serverPanic("Unknown btype in unblockClient().");
|
||||
}
|
||||
@@ -153,12 +155,15 @@ void unblockClient(client *c) {
|
||||
}
|
||||
|
||||
/* This function gets called when a blocked client timed out in order to
|
||||
* send it a reply of some kind. */
|
||||
* send it a reply of some kind. After this function is called,
|
||||
* unblockClient() will be called with the same client as argument. */
|
||||
void replyToBlockedClientTimedOut(client *c) {
|
||||
if (c->btype == BLOCKED_LIST) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else if (c->btype == BLOCKED_WAIT) {
|
||||
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
moduleBlockedClientTimedOut(c);
|
||||
} else {
|
||||
serverPanic("Unknown btype in replyToBlockedClientTimedOut().");
|
||||
}
|
||||
|
||||
+201
-12
@@ -105,6 +105,7 @@ struct RedisModuleCtx {
|
||||
int flags; /* REDISMODULE_CTX_... flags. */
|
||||
void **postponed_arrays; /* To set with RM_ReplySetArrayLength(). */
|
||||
int postponed_arrays_count; /* Number of entries in postponed_arrays. */
|
||||
void *blocked_privdata; /* Privdata set when unblocking a clinet. */
|
||||
|
||||
/* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST flag set. */
|
||||
int *keys_pos;
|
||||
@@ -114,10 +115,12 @@ struct RedisModuleCtx {
|
||||
};
|
||||
typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
|
||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, 0, NULL}
|
||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL}
|
||||
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
|
||||
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
|
||||
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
|
||||
#define REDISMODULE_CTX_BLOCKED_REPLY (1<<3)
|
||||
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
|
||||
|
||||
/* This represents a Redis key opened with RM_OpenKey(). */
|
||||
struct RedisModuleKey {
|
||||
@@ -183,6 +186,23 @@ typedef struct RedisModuleCallReply {
|
||||
} val;
|
||||
} RedisModuleCallReply;
|
||||
|
||||
/* Structure representing a blocked client. We get a pointer to such
|
||||
* an object when blocking from modules. */
|
||||
typedef struct RedisModuleBlockedClient {
|
||||
client *client; /* Pointer to the blocked client. or NULL if the client
|
||||
was destroyed during the life of this object. */
|
||||
RedisModule *module; /* Module blocking the client. */
|
||||
RedisModuleCmdFunc reply_callback; /* Reply callback on normal completion.*/
|
||||
RedisModuleCmdFunc timeout_callback; /* Reply callback on timeout. */
|
||||
void (*free_privdata)(void *); /* privdata cleanup callback. */
|
||||
void *privdata; /* Module private data that may be used by the reply
|
||||
or timeout callback. It is set via the
|
||||
RedisModule_UnblockClient() API. */
|
||||
} RedisModuleBlockedClient;
|
||||
|
||||
static pthread_mutex_t moduleUnblockedClientsMutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static list *moduleUnblockedClients;
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -403,6 +423,26 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for when a command callback is called, in order to handle
|
||||
* details needed to correctly replicate commands. */
|
||||
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||
client *c = ctx->client;
|
||||
|
||||
/* We don't want any automatic propagation here since in modules we handle
|
||||
* replication / AOF propagation in explicit ways. */
|
||||
preventCommandPropagation(c);
|
||||
|
||||
/* Handle the replication of the final EXEC, since whatever a command
|
||||
* emits is always wrappered around MULTI/EXEC. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/* This Redis command binds the normal Redis command invocation with commands
|
||||
* exported by modules. */
|
||||
void RedisModuleCommandDispatcher(client *c) {
|
||||
@@ -412,17 +452,7 @@ void RedisModuleCommandDispatcher(client *c) {
|
||||
ctx.module = cp->module;
|
||||
ctx.client = c;
|
||||
cp->func(&ctx,(void**)c->argv,c->argc);
|
||||
preventCommandPropagation(c);
|
||||
|
||||
/* Handle the replication of the final EXEC, since whatever a command
|
||||
* emits is always wrappered around MULTI/EXEC. */
|
||||
if (ctx.flags & REDISMODULE_CTX_MULTI_EMITTED) {
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
}
|
||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
|
||||
@@ -618,6 +648,11 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
|
||||
ctx->module = module;
|
||||
}
|
||||
|
||||
/* Return the current UNIX time in milliseconds. */
|
||||
long long RM_Milliseconds(void) {
|
||||
return mstime();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Automatic memory management for modules
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -3034,6 +3069,152 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Blocking clients from modules
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* This is called from blocked.c in order to unblock a client: may be called
|
||||
* for multiple reasons while the client is in the middle of being blocked
|
||||
* because the client is terminated, but is also called for cleanup when a
|
||||
* client is unblocked in a clean way after replaying.
|
||||
*
|
||||
* What we do here is just to set the client to NULL in the redis module
|
||||
* blocked client handle. This way if the client is terminated while there
|
||||
* is a pending threaded operation involving the blocked client, we'll know
|
||||
* that the client no longer exists and no reply callback should be called.
|
||||
*
|
||||
* The structure RedisModuleBlockedClient will be always deallocated when
|
||||
* running the list of clients blocked by a module that need to be unblocked. */
|
||||
void unblockClientFromModule(client *c) {
|
||||
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
|
||||
bc->client = NULL;
|
||||
}
|
||||
|
||||
/* Block a client in the context of a blocking command, returning an handle
|
||||
* which will be used, later, in order to block the client with a call to
|
||||
* RedisModule_UnblockClient(). The arguments specify callback functions
|
||||
* and a timeout after which the client is unblocked.
|
||||
*
|
||||
* The callbacks are called in the following contexts:
|
||||
*
|
||||
* reply_callback: called after a successful RedisModule_UnblockClient() call
|
||||
* in order to reply to the client and unblock it.
|
||||
* reply_timeout: called when the timeout is reached in order to send an
|
||||
* error to the client.
|
||||
* free_privdata: called in order to free the privata data that is passed
|
||||
* by RedisModule_UnblockClient() call.
|
||||
*/
|
||||
RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms) {
|
||||
client *c = ctx->client;
|
||||
c->bpop.module_blocked_handle = zmalloc(sizeof(RedisModuleBlockedClient));
|
||||
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
|
||||
|
||||
bc->client = c;
|
||||
bc->module = ctx->module;
|
||||
bc->reply_callback = reply_callback;
|
||||
bc->timeout_callback = timeout_callback;
|
||||
bc->free_privdata = free_privdata;
|
||||
bc->privdata = NULL;
|
||||
c->bpop.timeout = timeout_ms ? (mstime()+timeout_ms) : 0;
|
||||
|
||||
blockClient(c,BLOCKED_MODULE);
|
||||
return bc;
|
||||
}
|
||||
|
||||
/* Unblock a client blocked by `RedisModule_BlockedClient`. This will trigger
|
||||
* the reply callbacks to be called in order to reply to the client.
|
||||
* The 'privdata' argument will be accessible by the reply callback, so
|
||||
* the caller of this function can pass any value that is needed in order to
|
||||
* actually reply to the client.
|
||||
*
|
||||
* A common usage for 'privdata' is a thread that computes something that
|
||||
* needs to be passed to the client, included but not limited some slow
|
||||
* to compute reply or some reply obtained via networking.
|
||||
*
|
||||
* Note: this function can be called from threads spawned by the module. */
|
||||
int RM_UnblockClient(RedisModuleBlockedClient *bc, void *privdata) {
|
||||
pthread_mutex_lock(&moduleUnblockedClientsMutex);
|
||||
bc->privdata = privdata;
|
||||
listAddNodeTail(moduleUnblockedClients,bc);
|
||||
pthread_mutex_unlock(&moduleUnblockedClientsMutex);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* This function will check the moduleUnblockedClients queue in order to
|
||||
* call the reply callback and really unblock the client.
|
||||
*
|
||||
* Clients end into this list because of calls to RM_UnblockClient(),
|
||||
* however it is possible that while the module was doing work for the
|
||||
* blocked client, it was terminated by Redis (for timeout or other reasons).
|
||||
* When this happens the RedisModuleBlockedClient structure in the queue
|
||||
* will have the 'client' field set to NULL. */
|
||||
void moduleHandleBlockedClients(void) {
|
||||
listNode *ln;
|
||||
RedisModuleBlockedClient *bc;
|
||||
|
||||
pthread_mutex_lock(&moduleUnblockedClientsMutex);
|
||||
while (listLength(moduleUnblockedClients)) {
|
||||
ln = listFirst(moduleUnblockedClients);
|
||||
bc = ln->value;
|
||||
client *c = bc->client;
|
||||
listDelNode(moduleUnblockedClients,ln);
|
||||
pthread_mutex_unlock(&moduleUnblockedClientsMutex);
|
||||
|
||||
/* Release the lock during the loop, as long as we don't
|
||||
* touch the shared list. */
|
||||
|
||||
if (c != NULL) {
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.flags |= REDISMODULE_CTX_BLOCKED_REPLY;
|
||||
ctx.blocked_privdata = bc->privdata;
|
||||
ctx.module = bc->module;
|
||||
ctx.client = bc->client;
|
||||
bc->reply_callback(&ctx,(void**)c->argv,c->argc);
|
||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
if (bc->privdata && bc->free_privdata)
|
||||
bc->free_privdata(bc->privdata);
|
||||
zfree(bc);
|
||||
if (c != NULL) unblockClient(bc->client);
|
||||
|
||||
/* Lock again before to iterate the loop. */
|
||||
pthread_mutex_lock(&moduleUnblockedClientsMutex);
|
||||
}
|
||||
pthread_mutex_unlock(&moduleUnblockedClientsMutex);
|
||||
}
|
||||
|
||||
/* Called when our client timed out. After this function unblockClient()
|
||||
* is called, and it will invalidate the blocked client. So this function
|
||||
* does not need to do any cleanup. Eventually the module will call the
|
||||
* API to unblock the client and the memory will be released. */
|
||||
void moduleBlockedClientTimedOut(client *c) {
|
||||
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.flags |= REDISMODULE_CTX_BLOCKED_TIMEOUT;
|
||||
ctx.module = bc->module;
|
||||
ctx.client = bc->client;
|
||||
bc->timeout_callback(&ctx,(void**)c->argv,c->argc);
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
|
||||
/* Return non-zero if a module command was called in order to fill the
|
||||
* reply for a blocked client. */
|
||||
int RM_IsBlockedReplyRequest(RedisModuleCtx *ctx) {
|
||||
return (ctx->flags & REDISMODULE_CTX_BLOCKED_REPLY) != 0;
|
||||
}
|
||||
|
||||
/* Return non-zero if a module command was called in order to fill the
|
||||
* reply for a blocked client that timed out. */
|
||||
int RM_IsBlockedTimeoutRequest(RedisModuleCtx *ctx) {
|
||||
return (ctx->flags & REDISMODULE_CTX_BLOCKED_TIMEOUT) != 0;
|
||||
}
|
||||
|
||||
/* Get the privata data set by RedisModule_UnblockClient() */
|
||||
void *RM_GetBlockedClientPrivateData(RedisModuleCtx *ctx) {
|
||||
return ctx->blocked_privdata;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules API internals
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -3070,6 +3251,8 @@ int moduleRegisterApi(const char *funcname, void *funcptr) {
|
||||
void moduleRegisterCoreAPI(void);
|
||||
|
||||
void moduleInitModulesSystem(void) {
|
||||
moduleUnblockedClients = listCreate();
|
||||
|
||||
server.loadmodule_queue = listCreate();
|
||||
modules = dictCreate(&modulesDictType,NULL);
|
||||
moduleRegisterCoreAPI();
|
||||
@@ -3357,4 +3540,10 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(RetainString);
|
||||
REGISTER_API(StringCompare);
|
||||
REGISTER_API(GetContextFromIO);
|
||||
REGISTER_API(BlockClient);
|
||||
REGISTER_API(UnblockClient);
|
||||
REGISTER_API(IsBlockedReplyRequest);
|
||||
REGISTER_API(IsBlockedTimeoutRequest);
|
||||
REGISTER_API(GetBlockedClientPrivateData);
|
||||
REGISTER_API(Milliseconds);
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
# Compile flags for linux / osx
|
||||
ifeq ($(uname_S),Linux)
|
||||
SHOBJ_CFLAGS ?= -fno-common -g -ggdb -std=c99 -O2
|
||||
SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2
|
||||
SHOBJ_LDFLAGS ?= -shared
|
||||
else
|
||||
SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb -std=c99 -O2
|
||||
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: helloworld.so hellotype.so testmodule.so
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
@@ -28,6 +28,11 @@ hellotype.xo: ../redismodule.h
|
||||
hellotype.so: hellotype.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
helloblock.xo: ../redismodule.h
|
||||
|
||||
helloblock.so: helloblock.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lpthread -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
testmodule.so: testmodule.xo
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/* Helloblock module -- An example of blocking command implementation
|
||||
* with threads.
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2016, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "../redismodule.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Reply callback for blocking command HELLO.BLOCK */
|
||||
int HelloBlock_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int *myint = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
return RedisModule_ReplyWithLongLong(ctx,*myint);
|
||||
}
|
||||
|
||||
/* Timeout callback for blocking command HELLO.BLOCK */
|
||||
int HelloBlock_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return RedisModule_ReplyWithSimpleString(ctx,"Request timedout");
|
||||
}
|
||||
|
||||
/* Private data freeing callback for HELLO.BLOCK command. */
|
||||
void HelloBlock_FreeData(void *privdata) {
|
||||
RedisModule_Free(privdata);
|
||||
}
|
||||
|
||||
/* The thread entry point that actually executes the blocking part
|
||||
* of the command HELLO.BLOCK. */
|
||||
void *HelloBlock_ThreadMain(void *arg) {
|
||||
void **targ = arg;
|
||||
RedisModuleBlockedClient *bc = targ[0];
|
||||
long long delay = (unsigned long)targ[1];
|
||||
RedisModule_Free(targ);
|
||||
|
||||
sleep(delay);
|
||||
int *r = RedisModule_Alloc(sizeof(int));
|
||||
*r = rand();
|
||||
RedisModule_UnblockClient(bc,r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* HELLO.BLOCK <delay> <timeout> -- Block for <count> seconds, then reply with
|
||||
* a random number. Timeout is the command timeout, so that you can test
|
||||
* what happens when the delay is greater than the timeout. */
|
||||
int HelloBlock_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||
long long delay;
|
||||
long long timeout;
|
||||
|
||||
if (RedisModule_StringToLongLong(argv[1],&delay) != REDISMODULE_OK) {
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid count");
|
||||
}
|
||||
|
||||
if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK) {
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid count");
|
||||
}
|
||||
|
||||
pthread_t tid;
|
||||
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout);
|
||||
|
||||
/* Now that we setup a blocking client, we need to pass the control
|
||||
* to the thread. However we need to pass arguments to the thread:
|
||||
* the delay and a reference to the blocked client handle. */
|
||||
void **targ = RedisModule_Alloc(sizeof(void*)*2);
|
||||
targ[0] = bc;
|
||||
targ[1] = (void*)(unsigned long) delay;
|
||||
|
||||
if (pthread_create(&tid,NULL,HelloBlock_ThreadMain,targ) != 0) {
|
||||
/* RedisModule_BlockedClientAbort(bc); */
|
||||
return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* This function must be present on each Redis module. It is used in order to
|
||||
* register the commands into the Redis server. */
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (RedisModule_Init(ctx,"helloblock",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hello.block",
|
||||
HelloBlock_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -227,6 +227,8 @@ void HelloTypeAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value
|
||||
}
|
||||
|
||||
void HelloTypeDigest(RedisModuleDigest *digest, void *value) {
|
||||
REDISMODULE_NOT_USED(digest);
|
||||
REDISMODULE_NOT_USED(value);
|
||||
/* TODO: The DIGEST module interface is yet not implemented. */
|
||||
}
|
||||
|
||||
@@ -237,6 +239,9 @@ void HelloTypeFree(void *value) {
|
||||
/* This function must be present on each Redis module. It is used in order to
|
||||
* register the commands into the Redis server. */
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"hellotype",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ int TestMatchReply(RedisModuleCallReply *reply, char *str) {
|
||||
|
||||
/* TEST.CALL -- Test Call() API. */
|
||||
int TestCall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModule_AutoMemory(ctx);
|
||||
RedisModuleCallReply *reply;
|
||||
|
||||
@@ -75,6 +78,9 @@ fail:
|
||||
|
||||
/* TEST.STRING.APPEND -- Test appending to an existing string object. */
|
||||
int TestStringAppend(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModuleString *s = RedisModule_CreateString(ctx,"foo",3);
|
||||
RedisModule_StringAppendBuffer(ctx,s,"bar",3);
|
||||
RedisModule_ReplyWithString(ctx,s);
|
||||
@@ -84,6 +90,9 @@ int TestStringAppend(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
|
||||
/* TEST.STRING.APPEND.AM -- Test append with retain when auto memory is on. */
|
||||
int TestStringAppendAM(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModule_AutoMemory(ctx);
|
||||
RedisModuleString *s = RedisModule_CreateString(ctx,"foo",3);
|
||||
RedisModule_RetainString(ctx,s);
|
||||
@@ -163,6 +172,9 @@ int TestAssertIntegerReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, lon
|
||||
|
||||
/* TEST.IT -- Run all the tests. */
|
||||
int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModule_AutoMemory(ctx);
|
||||
RedisModuleCallReply *reply;
|
||||
|
||||
@@ -195,6 +207,9 @@ fail:
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"test",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
#define REDISMODULE_POSITIVE_INFINITE (1.0/0.0)
|
||||
#define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0)
|
||||
|
||||
#define REDISMODULE_NOT_USED(V) ((void) V)
|
||||
|
||||
/* ------------------------- End of common defines ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
@@ -82,6 +84,7 @@ typedef struct RedisModuleCallReply RedisModuleCallReply;
|
||||
typedef struct RedisModuleIO RedisModuleIO;
|
||||
typedef struct RedisModuleType RedisModuleType;
|
||||
typedef struct RedisModuleDigest RedisModuleDigest;
|
||||
typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
|
||||
@@ -192,6 +195,12 @@ 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);
|
||||
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
|
||||
@@ -293,6 +302,12 @@ 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(BlockClient);
|
||||
REDISMODULE_GET_API(UnblockClient);
|
||||
REDISMODULE_GET_API(IsBlockedReplyRequest);
|
||||
REDISMODULE_GET_API(IsBlockedTimeoutRequest);
|
||||
REDISMODULE_GET_API(GetBlockedClientPrivateData);
|
||||
REDISMODULE_GET_API(Milliseconds);
|
||||
|
||||
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
|
||||
return REDISMODULE_OK;
|
||||
|
||||
@@ -1195,6 +1195,10 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
if (listLength(server.clients_waiting_acks))
|
||||
processClientsWaitingReplicas();
|
||||
|
||||
/* Check if there are clients unblocked by modules that implement
|
||||
* blocking commands. */
|
||||
moduleHandleBlockedClients();
|
||||
|
||||
/* Try to process pending commands for clients that were just unblocked. */
|
||||
if (listLength(server.unblocked_clients))
|
||||
processUnblockedClients();
|
||||
|
||||
@@ -245,6 +245,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define BLOCKED_NONE 0 /* Not blocked, no CLIENT_BLOCKED flag set. */
|
||||
#define BLOCKED_LIST 1 /* BLPOP & co. */
|
||||
#define BLOCKED_WAIT 2 /* WAIT for synchronous replication. */
|
||||
#define BLOCKED_MODULE 3 /* Blocked by a loadable module. */
|
||||
|
||||
/* Client request types */
|
||||
#define PROTO_REQ_INLINE 1
|
||||
@@ -619,6 +620,11 @@ typedef struct blockingState {
|
||||
/* BLOCKED_WAIT */
|
||||
int numreplicas; /* Number of replicas we are waiting for ACK. */
|
||||
long long reploffset; /* Replication offset to reach. */
|
||||
|
||||
/* BLOCKED_MODULE */
|
||||
void *module_blocked_handle; /* RedisModuleBlockedClient structure.
|
||||
which is opaque for the Redis core, only
|
||||
handled in module.c. */
|
||||
} blockingState;
|
||||
|
||||
/* The following structure represents a node in the server.ready_keys list,
|
||||
@@ -1226,6 +1232,9 @@ int *moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc,
|
||||
moduleType *moduleTypeLookupModuleByID(uint64_t id);
|
||||
void moduleTypeNameByID(char *name, uint64_t moduleid);
|
||||
void moduleFreeContext(struct RedisModuleCtx *ctx);
|
||||
void unblockClientFromModule(client *c);
|
||||
void moduleHandleBlockedClients(void);
|
||||
void moduleBlockedClientTimedOut(client *c);
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
|
||||
Reference in New Issue
Block a user