Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Redis Plugins #451

Open
wants to merge 22 commits into from

1 participant

@spullara

What do you think of this in general? I would imagine you would want to write it yourself, but I like the idea of having all the commands available in Redis loaded dynamically and the ability to easily extend Redis by 3rd parties. This would probably much reduce the feature request load you get everyday.

@spullara

Oops, there is some JNI experimentation in there too, ignore that :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 7, 2011
  1. @spullara
  2. @spullara

    time to get replies to work

    spullara authored
  3. @spullara
  4. @spullara

    change to redisLog

    spullara authored
Commits on Mar 19, 2012
  1. @spullara

    remove the simple test

    spullara authored
Commits on Apr 12, 2012
  1. add plugins to redis

    Sam Pullara authored
  2. add plugins

    Sam Pullara authored
  3. now walks the plugin directory

    Sam Pullara authored
  4. add hello world and notice for registering plugins

    Sam Pullara authored
Commits on Apr 28, 2012
  1. Merge branch 'unstable' into plugins

    Sam Pullara authored
  2. accidentally pushed these

    Sam Pullara authored
  3. rename the test plugin to hello

    Sam Pullara authored
  4. get rid of the compiler warning

    Sam Pullara authored
  5. get rid of other files

    Sam Pullara authored
  6. update readme for people that land on the page

    Sam Pullara authored
  7. smpfy

    Sam Pullara authored
Commits on May 3, 2012
  1. Merge branch 'unstable' into plugins

    Sam Pullara authored
Commits on May 8, 2012
This page is out of date. Refresh to see the latest.
View
3  .gitignore
@@ -1,5 +1,8 @@
+*~
.*.swp
+*.dSYM
*.o
+*.so
*.rdb
*.log
redis-cli
View
29 README
@@ -1,3 +1,32 @@
+Making Redis Pluggable
+----------------------
+
+Have you ever wanted to add a new command to Redis? Or wrap an existing command
+with additional functionality? How about disabling an existing command? All
+these use cases are possible with this small change that allows you to
+register new commands or overwrite existing commands. Here is a trivial
+example of a plugin included with the change:
+
+#include "redis.h"
+
+void hello(redisClient *c) {
+ addReplyBulkCString(c, "world!");
+}
+
+struct redisCommand commands[] = {
+ {"hello",hello,1,"r",0,NULL,1,1,1,0,0}
+};
+
+struct redisCommand* registerCommands(int *numcommands) {
+ *numcommands = sizeof(commands)/sizeof(struct redisCommand);
+ return commands;
+}
+
+Your plugin will get a single callback on server start that requests an array
+of commands you wish to register. This works exactly the same way as the
+normal command registration within Redis. See redis.c for further documentation
+on what the arguments and commands mean in the redisCommand struct.
+
Where to find complete Redis documentation?
-------------------------------------------
View
63 plugins/Makefile
@@ -0,0 +1,63 @@
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+OPTIMIZATION?=-O2
+
+STD= -std=c99 -pedantic
+WARN= -Wall
+OPT= $(OPTIMIZATION)
+
+ifeq ($(uname_S),SunOS)
+ REDIS_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(ADD_CFLAGS) -D__EXTENSIONS__ -D_XPG6
+ REDIS_LDFLAGS= $(LDFLAGS) $(ADD_LDFLAGS)
+ REDIS_LIBS= $(LIBS) -ldl -lnsl -lsocket -lm -lpthread
+ DEBUG= -g -ggdb
+else
+ REDIS_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(ADD_CFLAGS)
+ REDIS_LDFLAGS= $(LDFLAGS) $(ADD_LDFLAGS)
+ REDIS_LIBS= $(LIBS) -lm -pthread
+ DEBUG= -g -rdynamic -ggdb
+endif
+
+# Include paths to dependencies
+REDIS_CFLAGS+= -I../src -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
+
+# Default allocator
+ifeq ($(uname_S),Linux)
+ MALLOC?=jemalloc
+else
+ MALLOC?=libc
+endif
+
+# Backwards compatibility for selecting an allocator
+ifeq ($(USE_TCMALLOC),yes)
+ MALLOC=tcmalloc
+endif
+
+ifeq ($(USE_TCMALLOC_MINIMAL),yes)
+ MALLOC=tcmalloc_minimal
+endif
+
+ifeq ($(USE_JEMALLOC),yes)
+ MALLOC=jemalloc
+endif
+
+ifeq ($(MALLOC),tcmalloc)
+ REDIS_CFLAGS+= -DUSE_TCMALLOC
+ REDIS_LIBS+= -ltcmalloc
+endif
+
+ifeq ($(MALLOC),tcmalloc_minimal)
+ REDIS_CFLAGS+= -DUSE_TCMALLOC
+ REDIS_LIBS+= -ltcmalloc_minimal
+endif
+
+ifeq ($(MALLOC),jemalloc)
+ DEPENDENCY_TARGETS+= jemalloc
+ REDIS_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
+ REDIS_LIBS+= ../deps/jemalloc/lib/libjemalloc.a -ldl
+endif
+
+REDIS_CC=$(QUIET_CC)$(CC) $(REDIS_CFLAGS)
+REDIS_LD=$(QUIET_LINK)$(CC) $(REDIS_LDFLAGS)
+
+plugin-hello.so: plugin-hello.c
+ $(REDIS_CC) -flat_namespace -bundle -undefined suppress -o libhello.so plugin-hello.c
View
15 plugins/plugin-hello.c
@@ -0,0 +1,15 @@
+#include "redis.h"
+
+void hello(redisClient *c) {
+ addReplyBulkCString(c, "world!");
+}
+
+struct redisCommand commands[] = {
+ {"hello",hello,1,"r",0,NULL,1,1,1,0,0}
+};
+
+struct redisCommand* registerCommands(int *numcommands) {
+ *numcommands = sizeof(commands)/sizeof(struct redisCommand);
+ return commands;
+}
+
View
1  redis.conf
@@ -542,3 +542,4 @@ client-output-buffer-limit pubsub 32mb 8mb 60
#
# include /path/to/local.conf
# include /path/to/other.conf
+plugindir plugins
View
2  src/config.c
@@ -324,6 +324,8 @@ void loadServerConfigFromString(char *config) {
server.slowlog_log_slower_than = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) {
server.slowlog_max_len = strtoll(argv[1],NULL,10);
+ } else if (!strcasecmp(argv[0],"plugindir") && argc == 2) {
+ server.plugindir = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"client-output-buffer-limit") &&
argc == 5)
{
View
30 src/redis.c
@@ -31,6 +31,8 @@
#include "slowlog.h"
#include "bio.h"
+#include <dirent.h>
+#include <dlfcn.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
@@ -1123,7 +1125,7 @@ void initServerConfig() {
* initial configuration, since command names may be changed via
* redis.conf using the rename-command directive. */
server.commands = dictCreate(&commandTableDictType,NULL);
- populateCommandTable();
+ populateCommandTable(redisCommandTable, sizeof(redisCommandTable)/sizeof(struct redisCommand));
server.delCommand = lookupCommandByCString("del");
server.multiCommand = lookupCommandByCString("multi");
server.lpushCommand = lookupCommandByCString("lpush");
@@ -1278,6 +1280,29 @@ void initServer() {
exit(1);
}
}
+ if (server.plugindir) {
+ redisLog(REDIS_NOTICE, "Plugins directory: %s", server.plugindir);
+ DIR *plugindir = opendir(server.plugindir);
+ struct dirent *dp;
+ while ((dp = readdir(plugindir)) != NULL) {
+ int len = dp->d_namlen;
+ if (len > 3 && !strncmp(dp->d_name + len - 3, ".so", 3)) {
+ char path[1024];
+ redisLog(REDIS_NOTICE, "Registering plugin: %s", dp->d_name);
+ snprintf(path, 1024, "plugins/%s", dp->d_name);
+ void *plugin = dlopen(path, RTLD_NOW);
+ // Directly casting the dlsym to a register plugin was
+ // annoying the compiler. See this thread:
+ // https://groups.google.com/group/comp.unix.programmer/msg/de36b126ba703795
+ registerPlugin registerCommand;
+ *(void **) (&registerCommand) = dlsym(plugin, "registerCommands");
+ int numcommands = 0;
+ struct redisCommand* commands = registerCommand(&numcommands);
+ populateCommandTable(commands, numcommands);
+ }
+ }
+ closedir(plugindir);
+ }
/* 32 bit instances are limited to 4GB of address space, so if there is
* no explicit limit in the user provided configuration we set a limit
@@ -1297,9 +1322,8 @@ void initServer() {
/* Populates the Redis Command Table starting from the hard coded list
* we have on top of redis.c file. */
-void populateCommandTable(void) {
+void populateCommandTable(struct redisCommand* redisCommandTable, int numcommands) {
int j;
- int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand);
for (j = 0; j < numcommands; j++) {
struct redisCommand *c = redisCommandTable+j;
View
5 src/redis.h
@@ -724,6 +724,7 @@ struct redisServer {
int assert_line;
int bug_report_start; /* True if bug report header was already logged. */
int watchdog_period; /* Software watchdog period in ms. 0 = off */
+ char *plugindir;
};
typedef struct pubsubPattern {
@@ -749,6 +750,8 @@ struct redisCommand {
long long microseconds, calls;
};
+typedef struct redisCommand* (*registerPlugin)(int*);
+
struct redisFunctionSym {
char *name;
unsigned long pointer;
@@ -1013,7 +1016,7 @@ void usage();
void updateDictResizePolicy(void);
int htNeedsResize(dict *dict);
void oom(const char *msg);
-void populateCommandTable(void);
+void populateCommandTable(struct redisCommand* redisCommandTable, int numcommands);
void resetCommandTableStats(void);
/* Set data type */
View
151 src/redis_jni_Redis.c
@@ -0,0 +1,151 @@
+#include <stdio.h>
+#include "sds.h"
+#include "redis.h"
+#include "redis_jni_Redis.h"
+
+redisClient *jniClient = 0;
+
+void beforeSleep(struct aeEventLoop *eventLoop);
+
+JNIEXPORT void JNICALL Java_redis_jni_Redis_start(JNIEnv *env, jclass class, jstring file) {
+ jboolean isCopy;
+ int argc = 2;
+ char **argv = zmalloc(sizeof(*argv)*2);
+ long long start;
+
+ if (!jniClient) {
+ // Start redis
+ argv[0] = "";
+ argv[1] = (*env)->GetStringUTFChars(env, file, &isCopy);
+ printf("Config: %s\n", argv[1]);
+
+ zmalloc_enable_thread_safeness();
+ initServerConfig();
+ resetServerSaveParams();
+ loadServerConfig(argv[1]);
+ initServer();
+ redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION);
+#ifdef __linux__
+ linuxOvercommitMemoryWarning();
+#endif
+ start = ustime();
+ if (server.appendonly) {
+ if (loadAppendOnlyFile(server.appendfilename) == REDIS_OK)
+ redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
+ } else {
+ if (rdbLoad(server.dbfilename) == REDIS_OK) {
+ redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds", (float)(ustime()-start)/1000000);
+ } else if (errno != ENOENT) {
+ redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting.");
+ exit(1);
+ }
+ }
+ if (server.ipfd > 0)
+ redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
+ if (server.sofd > 0)
+ redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
+ aeSetBeforeSleepProc(server.el,beforeSleep);
+ // Pretend to be the in-proc LUA client
+ jniClient = createClient(-1);
+ jniClient->flags |= REDIS_LUA_CLIENT;
+ selectDb(jniClient, 0);
+ redisLog(REDIS_NOTICE, "Redis client initialized\n");
+ }
+}
+
+JNIEXPORT void JNICALL Java_redis_jni_Redis_eventloop(JNIEnv *env, jclass class) {
+ redisLog(REDIS_NOTICE, "Starting Redis eventloop\n");
+ aeMain(server.el);
+ aeDeleteEventLoop(server.el);
+}
+
+JNIEXPORT jbyteArray JNICALL Java_redis_jni_Redis_command(JNIEnv *env, jclass class, jobjectArray paramArray) {
+ int j, argc;
+ struct redisCommand *cmd;
+ robj **argv;
+ jobject *params;
+ jbyte **bytes;
+ redisClient *c = jniClient;
+ jboolean isCopy;
+ sds reply;
+ int len = 0;
+ int replylen = 0;
+ jbyteArray result;
+
+ argc = (*env)->GetArrayLength(env, paramArray);
+
+ /* Build the arguments vector */
+ argv = zmalloc(sizeof(robj*)*argc);
+ params = zmalloc(sizeof(robj*)*argc);
+ bytes = zmalloc(sizeof(robj*)*argc);
+ for (j = 0; j < argc; j++) {
+ params[j] = (*env)->GetObjectArrayElement(env, paramArray, j);
+ bytes[j] = (*env)->GetByteArrayElements(env, params[j], &isCopy);
+ argv[j] = createStringObject(bytes[j], (*env)->GetArrayLength(env, params[j]));
+ }
+
+ /* Setup our fake client for command execution */
+ c->argv = argv;
+ c->argc = argc;
+
+ /* Command lookup */
+ cmd = lookupCommand(argv[0]->ptr);
+
+ if (!cmd || ((cmd->arity > 0 && cmd->arity != argc) ||
+ (argc < -cmd->arity)))
+ {
+ if (cmd)
+ redisLog(REDIS_ERR, "Wrong number of args calling Redis command\n");
+ else
+ redisLog(REDIS_ERR, "Unknown Redis command called\n");
+ goto cleanup;
+ }
+
+ if (cmd->flags & REDIS_CMD_NOSCRIPT) {
+ redisLog(REDIS_ERR, "This Redis command is not allowed from scripts");
+ goto cleanup;
+ }
+
+ if (cmd->flags & REDIS_CMD_WRITE && server.lua_random_dirty) {
+ redisLog(REDIS_ERR, "Write commands not allowed after non deterministic commands\n");
+ goto cleanup;
+ }
+
+ if (cmd->flags & REDIS_CMD_RANDOM) server.lua_random_dirty = 1;
+
+ /* Run the command */
+ cmd->proc(c);
+
+ reply = sdsempty();
+ if (c->bufpos) {
+ reply = sdscatlen(reply,c->buf,c->bufpos);
+ len += c->bufpos;
+ c->bufpos = 0;
+ }
+ while(listLength(c->reply)) {
+ robj *o = listNodeValue(listFirst(c->reply));
+ replylen = sdslen(o->ptr);
+ reply = sdscatlen(reply, o->ptr, replylen);
+ len += replylen;
+ listDelNode(c->reply, listFirst(c->reply));
+ }
+
+cleanup:
+ /* Clean up. Command code may have changed argv/argc so we use the
+ * argv/argc of the client instead of the local variables. */
+ for (j = 0; j < c->argc; j++) {
+ decrRefCount(c->argv[j]);
+ (*env)->ReleaseByteArrayElements(env, params[j], bytes[j], 0);
+ }
+ zfree(c->argv);
+ zfree(params);
+ zfree(bytes);
+
+ if (reply) {
+ result = (*env)->NewByteArray(env, len);
+ (*env)->SetByteArrayRegion(env, result, 0, len, reply);
+ return result;
+ } else {
+ return (*env)->NewByteArray(env, 0);
+ }
+}
View
37 src/redis_jni_Redis.h
@@ -0,0 +1,37 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class redis_jni_Redis */
+
+#ifndef _Included_redis_jni_Redis
+#define _Included_redis_jni_Redis
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: redis_jni_Redis
+ * Method: start
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL Java_redis_jni_Redis_start
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: redis_jni_Redis
+ * Method: eventloop
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_redis_jni_Redis_eventloop
+ (JNIEnv *, jclass);
+
+/*
+ * Class: redis_jni_Redis
+ * Method: command
+ * Signature: ([[B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_redis_jni_Redis_command
+ (JNIEnv *, jclass, jobjectArray);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
Something went wrong with that request. Please try again.