Redis module development environment
This document outlines the set up of a development environment for Redis Modules.
Prerequisites
- A Linux server (e.g. Ubuntu Gnome 16.10)
- Packages:
sudo apt install build-essential git
- Redis unstable:
git clone git@github.com:antirez/redis.git; cd redis; make
An example module
-
Create the project's directory:
mkdir example; cd example
-
Get the API's header file:
cp ../redis/src/redismodule.h .
-
Edit
example.c
:#include "redismodule.h" int Echo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { if (argc != 2) return RedisModule_WrongArity(ctx); return RedisModule_ReplyWithString(ctx, argv[1]); } int RedisModule_OnLoad(RedisModuleCtx *ctx) { if (RedisModule_Init(ctx, "example", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx, "example.echo", Echo, "readonly", 0, 0, 0) == REDISMODULE_ERR) return REDISMODULE_ERR; return REDISMODULE_OK; }
-
Compile it:
gcc -fPIC -std=gnu99 -c -o example.o example.c
-
Link it:
ld -o example.so example.o -shared -Bsymbolic -lc
-
Load it:
../redis/src/redis-server --loadmodule ./example.so
-
Test it:
../redis/src/redis-cli example.echo tango
Adding a makefile
-
Edit
Makefile
:SHOBJ_CFLAGS ?= -fno-common -g -ggdb SHOBJ_LDFLAGS ?= -shared -Bsymbolic CFLAGS = -Wall -g -fPIC -lc -lm -Og -std=gnu99 CC=gcc all: example.so example.so: example.o $(LD) -o $@ example.o $(SHOBJ_LDFLAGS) $(LIBS) -lc clean: rm -rf *.xo *.so *.o
Redis Modules SDK
Tools, utilities and scripts to help you write Redis modules!
git clone git@github.com:RedisLabs/RedisModulesSDK.git
Includes:
- A ready-to-use
example
module - The API's documentation
- String, memory management and testing utilties
- Vector, heap and priority queue implementations
- More... (and accepting pull requests)
Testing the module
The a simple approach is to have the module expose an entry point for running basic sanity tests:
-
Register a
test
command for the module by adding this toRedisModule_OnLoad
:if (RedisModule_CreateCommand(ctx, "example.test", Test, "readonly", 0, 0, 0) == REDISMODULE_ERR) return REDISMODULE_ERR;
-
Implement the
Test
function:int Test(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argc); RedisModule_AutoMemory(ctx); RedisModuleString *str = RedisModule_CreateString(ctx, "test", 4); RedisModuleCallReply *reply = RedisModule_Call(ctx, "example.echo", "s", str); if (REDISMODULE_REPLY_STRING != RedisModule_CallReplyType(reply)) { RedisModule_ReplyWithError(ctx, "Unexpected reply type"); return REDISMODULE_ERR; } RedisModuleString *rstr = RedisModule_CreateStringFromCallReply(reply); if (RedisModule_StringCompare(str, rstr) != 0) { RedisModule_ReplyWithError(ctx, "Unexpected reply"); return REDISMODULE_ERR; } RedisModule_ReplyWithSimpleString(ctx, "OK"); return REDISMODULE_OK; }
Some notes:
- Refer to
redis/src/modules/testmodule.c
and the SDK for a more implementation examples. - Test execution requires a running server, the module loaded and a client -> extra effort needed to automate unit tests.
- Can be extended/augmented/replaced with other testing frameworks. For example, RediSearch uses pytest and a disposable Redis class.
Setting up Visual Studio Code
-
You'll also need
clang
:sudo apt install clang
-
Run it and open the project's folder
-
Install extensions (
Ctrl+Shift+X
):- C/++ (
ms-vscode.cpptools
) - C/C++ Clang (
mitaki28.vscode-clang
)
- C/++ (
-
Configure the launcher's run task:
- Hit
F5
- Select "C++ (GDB/LLDB)"
- Edit
launch.json
to:
{ "version": "0.2.0", "configurations": [ { "name": "Launch Redis with module", "type": "cppdbg", "request": "launch", "program": "${workspaceRoot}/../redis/src/redis-server", "args": ["--loadmodule ${workspaceRoot}/example.so"], "stopAtEntry": false, "cwd": "${workspaceRoot}", "environment": [], "internalConsoleOptions": "openOnSessionStart", "linux": { "MIMode": "gdb" } } ] }
- Hit
-
Configure the build task:
Ctrl+Shift+B
-> Configure Task Runner- Select "Others"
- Edit
tasks.json
to:
{ "version": "0.1.0", "command": "make", "isShellCommand": true, "tasks": [ { "taskName": "all", "isBuildCommand": true } ] }
#include "redismodule.h" | |
int Echo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) | |
{ | |
if (argc != 2) | |
return RedisModule_WrongArity(ctx); | |
return RedisModule_ReplyWithString(ctx, argv[1]); | |
} | |
int Test(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { | |
REDISMODULE_NOT_USED(argv); | |
REDISMODULE_NOT_USED(argc); | |
RedisModule_AutoMemory(ctx); | |
RedisModuleString *str = RedisModule_CreateString(ctx, "test", 4); | |
RedisModuleCallReply *reply = RedisModule_Call(ctx, "example.echo", "s", str); | |
if (REDISMODULE_REPLY_STRING != RedisModule_CallReplyType(reply)) { | |
RedisModule_ReplyWithError(ctx, "Unexpected reply type"); | |
return REDISMODULE_ERR; | |
} | |
RedisModuleString *rstr = RedisModule_CreateStringFromCallReply(reply); | |
if (RedisModule_StringCompare(str, rstr) != 0) { | |
RedisModule_ReplyWithError(ctx, "Unexpected reply"); | |
return REDISMODULE_ERR; | |
} | |
RedisModule_ReplyWithSimpleString(ctx, "OK"); | |
return REDISMODULE_OK; | |
} | |
int RedisModule_OnLoad(RedisModuleCtx *ctx) | |
{ | |
if (RedisModule_Init(ctx, "example", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) | |
return REDISMODULE_ERR; | |
if (RedisModule_CreateCommand(ctx, "example.echo", Echo, "readonly", 0, 0, 0) == REDISMODULE_ERR) | |
return REDISMODULE_ERR; | |
if (RedisModule_CreateCommand(ctx, "example.test", Test, "readonly", 0, 0, 0) == REDISMODULE_ERR) | |
return REDISMODULE_ERR; | |
return REDISMODULE_OK; | |
} |
SHOBJ_CFLAGS ?= -fno-common -g -ggdb | |
SHOBJ_LDFLAGS ?= -shared -Bsymbolic | |
CFLAGS = -Wall -g -fPIC -lc -lm -Og -std=gnu99 | |
CC=gcc | |
all: example.so | |
example.so: example.o | |
$(LD) -o $@ example.o $(SHOBJ_LDFLAGS) $(LIBS) -lc | |
clean: | |
rm -rf *.xo *.so *.o |