Skip to content

Instantly share code, notes, and snippets.

@itamarhaber
Last active August 8, 2023 15:47
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save itamarhaber/6239d81e80b6dcfc33237dd1c2db142d to your computer and use it in GitHub Desktop.
Save itamarhaber/6239d81e80b6dcfc33237dd1c2db142d to your computer and use it in GitHub Desktop.
Module development environment walkthrough

Redis module development environment

This document outlines the set up of a development environment for Redis Modules.

Prerequisites

  1. A Linux server (e.g. Ubuntu Gnome 16.10)
  2. Packages: sudo apt install build-essential git
  3. Redis unstable: git clone git@github.com:antirez/redis.git; cd redis; make

An example module

  1. Create the project's directory: mkdir example; cd example

  2. Get the API's header file: cp ../redis/src/redismodule.h .

  3. 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;
    }
  4. Compile it: gcc -fPIC -std=gnu99 -c -o example.o example.c

  5. Link it: ld -o example.so example.o -shared -Bsymbolic -lc

  6. Load it: ../redis/src/redis-server --loadmodule ./example.so

  7. Test it: ../redis/src/redis-cli example.echo tango

Adding a makefile

  1. 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:

  1. Register a test command for the module by adding this to RedisModule_OnLoad:

    if (RedisModule_CreateCommand(ctx, "example.test", Test, "readonly", 0, 0, 0) == REDISMODULE_ERR)
        return REDISMODULE_ERR;
  2. 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

  1. Get Visual Studio Code

  2. You'll also need clang: sudo apt install clang

  3. Run it and open the project's folder

  4. Install extensions (Ctrl+Shift+X):

    1. C/++ (ms-vscode.cpptools)
    2. C/C++ Clang (mitaki28.vscode-clang)
  5. Configure the launcher's run task:

    1. Hit F5
    2. Select "C++ (GDB/LLDB)"
    3. 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"
                }
            }
        ]
    }
  6. Configure the build task:

    1. Ctrl+Shift+B -> Configure Task Runner
    2. Select "Others"
    3. 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment