aboutsummaryrefslogtreecommitdiff
path: root/examples/redis-unstable/tests/modules
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:40:55 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:40:55 +0100
commit5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda (patch)
tree1acdfa5220cd13b7be43a2a01368e80d306473ca /examples/redis-unstable/tests/modules
parentc7ab12bba64d9c20ccd79b132dac475f7bc3923e (diff)
downloadcrep-5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda.tar.gz
Add Redis source code for testing
Diffstat (limited to 'examples/redis-unstable/tests/modules')
-rw-r--r--examples/redis-unstable/tests/modules/Makefile106
-rw-r--r--examples/redis-unstable/tests/modules/aclcheck.c365
-rw-r--r--examples/redis-unstable/tests/modules/atomicslotmigration.c594
-rw-r--r--examples/redis-unstable/tests/modules/auth.c286
-rw-r--r--examples/redis-unstable/tests/modules/basics.c1051
-rw-r--r--examples/redis-unstable/tests/modules/blockedclient.c723
-rw-r--r--examples/redis-unstable/tests/modules/blockonbackground.c333
-rw-r--r--examples/redis-unstable/tests/modules/blockonkeys.c645
-rw-r--r--examples/redis-unstable/tests/modules/cmdintrospection.c226
-rw-r--r--examples/redis-unstable/tests/modules/commandfilter.c251
-rw-r--r--examples/redis-unstable/tests/modules/configaccess.c353
-rw-r--r--examples/redis-unstable/tests/modules/crash.c311
-rw-r--r--examples/redis-unstable/tests/modules/datatype.c323
-rw-r--r--examples/redis-unstable/tests/modules/datatype2.c739
-rw-r--r--examples/redis-unstable/tests/modules/defragtest.c475
-rw-r--r--examples/redis-unstable/tests/modules/eventloop.c276
-rw-r--r--examples/redis-unstable/tests/modules/fork.c96
-rw-r--r--examples/redis-unstable/tests/modules/getchannels.c69
-rw-r--r--examples/redis-unstable/tests/modules/getkeys.c178
-rw-r--r--examples/redis-unstable/tests/modules/hash.c244
-rw-r--r--examples/redis-unstable/tests/modules/hooks.c496
-rw-r--r--examples/redis-unstable/tests/modules/infotest.c119
-rw-r--r--examples/redis-unstable/tests/modules/internalsecret.c154
-rw-r--r--examples/redis-unstable/tests/modules/keyspace_events.c479
-rw-r--r--examples/redis-unstable/tests/modules/keyspecs.c236
-rw-r--r--examples/redis-unstable/tests/modules/list.c252
-rw-r--r--examples/redis-unstable/tests/modules/mallocsize.c237
-rw-r--r--examples/redis-unstable/tests/modules/misc.c642
-rw-r--r--examples/redis-unstable/tests/modules/moduleauthtwo.c43
-rw-r--r--examples/redis-unstable/tests/modules/moduleconfigs.c287
-rw-r--r--examples/redis-unstable/tests/modules/moduleconfigstwo.c39
-rw-r--r--examples/redis-unstable/tests/modules/postnotifications.c289
-rw-r--r--examples/redis-unstable/tests/modules/propagate.c403
-rw-r--r--examples/redis-unstable/tests/modules/publish.c57
-rw-r--r--examples/redis-unstable/tests/modules/rdbloadsave.c162
-rw-r--r--examples/redis-unstable/tests/modules/reply.c214
-rw-r--r--examples/redis-unstable/tests/modules/scan.c121
-rw-r--r--examples/redis-unstable/tests/modules/stream.c258
-rw-r--r--examples/redis-unstable/tests/modules/subcommands.c119
-rw-r--r--examples/redis-unstable/tests/modules/test_keymeta.c585
-rw-r--r--examples/redis-unstable/tests/modules/test_lazyfree.c196
-rw-r--r--examples/redis-unstable/tests/modules/testrdb.c405
-rw-r--r--examples/redis-unstable/tests/modules/timer.c102
-rw-r--r--examples/redis-unstable/tests/modules/usercall.c230
-rw-r--r--examples/redis-unstable/tests/modules/zset.c180
45 files changed, 13949 insertions, 0 deletions
diff --git a/examples/redis-unstable/tests/modules/Makefile b/examples/redis-unstable/tests/modules/Makefile
new file mode 100644
index 0000000..f141234
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/Makefile
@@ -0,0 +1,106 @@
1
2# find the OS
3uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
4
5warning_cflags = -W -Wall -Wno-missing-field-initializers
6ifeq ($(uname_S),Darwin)
7 SHOBJ_CFLAGS ?= $(warning_cflags) -dynamic -fno-common -g -ggdb -std=gnu11 -O2
8 SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
9else # Linux, others
10 SHOBJ_CFLAGS ?= $(warning_cflags) -fno-common -g -ggdb -std=gnu11 -O2
11 SHOBJ_LDFLAGS ?= -shared
12endif
13
14CLANG := $(findstring clang,$(shell sh -c '$(CC) --version | head -1'))
15
16ifeq ($(SANITIZER),memory)
17ifeq (clang, $(CLANG))
18 LD=clang
19 MALLOC=libc
20 CFLAGS+=-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-sanitize-recover=all -fno-omit-frame-pointer
21 LDFLAGS+=-fsanitize=memory
22else
23 $(error "MemorySanitizer needs to be compiled and linked with clang. Please use CC=clang")
24endif
25endif
26
27
28# This is a hack to override the default CC. When running with SANITIZER=memory
29# tough we want to keep the compiler as clang as MSan is not supported for gcc
30ifeq ($(uname_S),Linux)
31ifneq ($(SANITIZER),memory)
32 LD = gcc
33 CC = gcc
34endif
35endif
36
37# OS X 11.x doesn't have /usr/lib/libSystem.dylib and needs an explicit setting.
38ifeq ($(uname_S),Darwin)
39ifeq ("$(wildcard /usr/lib/libSystem.dylib)","")
40LIBS = -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lsystem
41endif
42endif
43
44TEST_MODULES = \
45 commandfilter.so \
46 basics.so \
47 testrdb.so \
48 fork.so \
49 infotest.so \
50 propagate.so \
51 misc.so \
52 hooks.so \
53 blockonkeys.so \
54 blockonbackground.so \
55 scan.so \
56 datatype.so \
57 datatype2.so \
58 auth.so \
59 keyspace_events.so \
60 blockedclient.so \
61 getkeys.so \
62 getchannels.so \
63 test_lazyfree.so \
64 timer.so \
65 defragtest.so \
66 keyspecs.so \
67 hash.so \
68 zset.so \
69 stream.so \
70 mallocsize.so \
71 aclcheck.so \
72 list.so \
73 subcommands.so \
74 reply.so \
75 cmdintrospection.so \
76 eventloop.so \
77 moduleconfigs.so \
78 moduleconfigstwo.so \
79 publish.so \
80 usercall.so \
81 postnotifications.so \
82 moduleauthtwo.so \
83 rdbloadsave.so \
84 crash.so \
85 internalsecret.so \
86 configaccess.so \
87 test_keymeta.so \
88 atomicslotmigration.so
89
90.PHONY: all
91
92all: $(TEST_MODULES)
93
9432bit:
95 $(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
96
97%.xo: %.c ../../src/redismodule.h
98 $(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
99
100%.so: %.xo
101 $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LDFLAGS) $(LIBS)
102
103.PHONY: clean
104
105clean:
106 rm -f $(TEST_MODULES) $(TEST_MODULES:.so=.xo)
diff --git a/examples/redis-unstable/tests/modules/aclcheck.c b/examples/redis-unstable/tests/modules/aclcheck.c
new file mode 100644
index 0000000..d7e4e3b
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/aclcheck.c
@@ -0,0 +1,365 @@
1
2#include "redismodule.h"
3#include <errno.h>
4#include <assert.h>
5#include <string.h>
6#include <strings.h>
7
8/* A wrap for SET command with ACL check on the key. */
9int set_aclcheck_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
10 if (argc < 4) {
11 return RedisModule_WrongArity(ctx);
12 }
13
14 int permissions;
15 const char *flags = RedisModule_StringPtrLen(argv[1], NULL);
16
17 if (!strcasecmp(flags, "W")) {
18 permissions = REDISMODULE_CMD_KEY_UPDATE;
19 } else if (!strcasecmp(flags, "R")) {
20 permissions = REDISMODULE_CMD_KEY_ACCESS;
21 } else if (!strcasecmp(flags, "*")) {
22 permissions = REDISMODULE_CMD_KEY_UPDATE | REDISMODULE_CMD_KEY_ACCESS;
23 } else if (!strcasecmp(flags, "~")) {
24 permissions = 0; /* Requires either read or write */
25 } else {
26 RedisModule_ReplyWithError(ctx, "INVALID FLAGS");
27 return REDISMODULE_OK;
28 }
29
30 /* Check that the key can be accessed */
31 RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx);
32 RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name);
33 int ret = RedisModule_ACLCheckKeyPermissions(user, argv[2], permissions);
34 if (ret != 0) {
35 RedisModule_ReplyWithError(ctx, "DENIED KEY");
36 RedisModule_FreeModuleUser(user);
37 RedisModule_FreeString(ctx, user_name);
38 return REDISMODULE_OK;
39 }
40
41 RedisModuleCallReply *rep = RedisModule_Call(ctx, "SET", "v", argv + 2, (size_t)argc - 2);
42 if (!rep) {
43 RedisModule_ReplyWithError(ctx, "NULL reply returned");
44 } else {
45 RedisModule_ReplyWithCallReply(ctx, rep);
46 RedisModule_FreeCallReply(rep);
47 }
48
49 RedisModule_FreeModuleUser(user);
50 RedisModule_FreeString(ctx, user_name);
51 return REDISMODULE_OK;
52}
53
54/* A wrap for SET command with ACL check on the key. */
55int set_aclcheck_prefixkey(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
56 if (argc < 4) {
57 return RedisModule_WrongArity(ctx);
58 }
59
60 int permissions;
61 const char *flags = RedisModule_StringPtrLen(argv[1], NULL);
62
63 if (!strcasecmp(flags, "W")) {
64 permissions = REDISMODULE_CMD_KEY_UPDATE;
65 } else if (!strcasecmp(flags, "R")) {
66 permissions = REDISMODULE_CMD_KEY_ACCESS;
67 } else if (!strcasecmp(flags, "*")) {
68 permissions = REDISMODULE_CMD_KEY_UPDATE | REDISMODULE_CMD_KEY_ACCESS;
69 } else if (!strcasecmp(flags, "~")) {
70 permissions = 0; /* Requires either read or write */
71 } else {
72 RedisModule_ReplyWithError(ctx, "INVALID FLAGS");
73 return REDISMODULE_OK;
74 }
75
76 /* Check that the key can be accessed */
77 RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx);
78 RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name);
79 int ret = RedisModule_ACLCheckKeyPrefixPermissions(user, argv[2], permissions);
80 if (ret != 0) {
81 RedisModule_ReplyWithError(ctx, "DENIED KEY");
82 RedisModule_FreeModuleUser(user);
83 RedisModule_FreeString(ctx, user_name);
84 return REDISMODULE_OK;
85 }
86
87 RedisModuleCallReply *rep = RedisModule_Call(ctx, "SET", "v", argv + 3, (size_t)argc - 3);
88 if (!rep) {
89 RedisModule_ReplyWithError(ctx, "NULL reply returned");
90 } else {
91 RedisModule_ReplyWithCallReply(ctx, rep);
92 RedisModule_FreeCallReply(rep);
93 }
94
95 RedisModule_FreeModuleUser(user);
96 RedisModule_FreeString(ctx, user_name);
97 return REDISMODULE_OK;
98}
99
100/* A wrap for PUBLISH command with ACL check on the channel. */
101int publish_aclcheck_channel(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
102 if (argc != 3) {
103 return RedisModule_WrongArity(ctx);
104 }
105
106 /* Check that the pubsub channel can be accessed */
107 RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx);
108 RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name);
109 int ret = RedisModule_ACLCheckChannelPermissions(user, argv[1], REDISMODULE_CMD_CHANNEL_SUBSCRIBE);
110 if (ret != 0) {
111 RedisModule_ReplyWithError(ctx, "DENIED CHANNEL");
112 RedisModule_FreeModuleUser(user);
113 RedisModule_FreeString(ctx, user_name);
114 return REDISMODULE_OK;
115 }
116
117 RedisModuleCallReply *rep = RedisModule_Call(ctx, "PUBLISH", "v", argv + 1, (size_t)argc - 1);
118 if (!rep) {
119 RedisModule_ReplyWithError(ctx, "NULL reply returned");
120 } else {
121 RedisModule_ReplyWithCallReply(ctx, rep);
122 RedisModule_FreeCallReply(rep);
123 }
124
125 RedisModule_FreeModuleUser(user);
126 RedisModule_FreeString(ctx, user_name);
127 return REDISMODULE_OK;
128}
129
130/* A wrap for RM_Call that check first that the command can be executed */
131int rm_call_aclcheck_cmd(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleString **argv, int argc) {
132 if (argc < 2) {
133 return RedisModule_WrongArity(ctx);
134 }
135
136 /* Check that the command can be executed */
137 int ret = RedisModule_ACLCheckCommandPermissions(user, argv + 1, argc - 1);
138 if (ret != 0) {
139 RedisModule_ReplyWithError(ctx, "DENIED CMD");
140 /* Add entry to ACL log */
141 RedisModule_ACLAddLogEntry(ctx, user, argv[1], REDISMODULE_ACL_LOG_CMD);
142 return REDISMODULE_OK;
143 }
144
145 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
146
147 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "v", argv + 2, (size_t)argc - 2);
148 if(!rep){
149 RedisModule_ReplyWithError(ctx, "NULL reply returned");
150 }else{
151 RedisModule_ReplyWithCallReply(ctx, rep);
152 RedisModule_FreeCallReply(rep);
153 }
154
155 return REDISMODULE_OK;
156}
157
158int rm_call_aclcheck_cmd_default_user(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
159 RedisModuleString *user_name = RedisModule_GetCurrentUserName(ctx);
160 RedisModuleUser *user = RedisModule_GetModuleUserFromUserName(user_name);
161
162 int res = rm_call_aclcheck_cmd(ctx, user, argv, argc);
163
164 RedisModule_FreeModuleUser(user);
165 RedisModule_FreeString(ctx, user_name);
166 return res;
167}
168
169int rm_call_aclcheck_cmd_module_user(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
170 /* Create a user and authenticate */
171 RedisModuleUser *user = RedisModule_CreateModuleUser("testuser1");
172 RedisModule_SetModuleUserACL(user, "allcommands");
173 RedisModule_SetModuleUserACL(user, "allkeys");
174 RedisModule_SetModuleUserACL(user, "on");
175 RedisModule_AuthenticateClientWithUser(ctx, user, NULL, NULL, NULL);
176
177 int res = rm_call_aclcheck_cmd(ctx, user, argv, argc);
178
179 /* authenticated back to "default" user (so once we free testuser1 we will not disconnected */
180 RedisModule_AuthenticateClientWithACLUser(ctx, "default", 7, NULL, NULL, NULL);
181 RedisModule_FreeModuleUser(user);
182 return res;
183}
184
185int rm_call_aclcheck_with_errors(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
186 REDISMODULE_NOT_USED(argv);
187 REDISMODULE_NOT_USED(argc);
188
189 if(argc < 2){
190 return RedisModule_WrongArity(ctx);
191 }
192
193 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
194
195 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "vEC", argv + 2, (size_t)argc - 2);
196 RedisModule_ReplyWithCallReply(ctx, rep);
197 RedisModule_FreeCallReply(rep);
198 return REDISMODULE_OK;
199}
200
201/* A wrap for RM_Call that pass the 'C' flag to do ACL check on the command. */
202int rm_call_aclcheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
203 REDISMODULE_NOT_USED(argv);
204 REDISMODULE_NOT_USED(argc);
205
206 if(argc < 2){
207 return RedisModule_WrongArity(ctx);
208 }
209
210 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
211
212 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "vC", argv + 2, (size_t)argc - 2);
213 if(!rep) {
214 char err[100];
215 switch (errno) {
216 case EACCES:
217 RedisModule_ReplyWithError(ctx, "ERR NOPERM");
218 break;
219 default:
220 snprintf(err, sizeof(err) - 1, "ERR errno=%d", errno);
221 RedisModule_ReplyWithError(ctx, err);
222 break;
223 }
224 } else {
225 RedisModule_ReplyWithCallReply(ctx, rep);
226 RedisModule_FreeCallReply(rep);
227 }
228
229 return REDISMODULE_OK;
230}
231
232int module_test_acl_category(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
233 REDISMODULE_NOT_USED(argv);
234 REDISMODULE_NOT_USED(argc);
235 RedisModule_ReplyWithSimpleString(ctx, "OK");
236 return REDISMODULE_OK;
237}
238
239int commandBlockCheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
240 REDISMODULE_NOT_USED(argv);
241 REDISMODULE_NOT_USED(argc);
242 int response_ok = 0;
243 int result = RedisModule_CreateCommand(ctx,"command.that.should.fail", module_test_acl_category, "", 0, 0, 0);
244 response_ok |= (result == REDISMODULE_OK);
245
246 result = RedisModule_AddACLCategory(ctx,"blockedcategory");
247 response_ok |= (result == REDISMODULE_OK);
248
249 RedisModuleCommand *parent = RedisModule_GetCommand(ctx,"block.commands.outside.onload");
250 result = RedisModule_SetCommandACLCategories(parent, "write");
251 response_ok |= (result == REDISMODULE_OK);
252
253 result = RedisModule_CreateSubcommand(parent,"subcommand.that.should.fail",module_test_acl_category,"",0,0,0);
254 response_ok |= (result == REDISMODULE_OK);
255
256 /* This validates that it's not possible to create commands or add
257 * a new ACL Category outside OnLoad function.
258 * thus returns an error if they succeed. */
259 if (response_ok) {
260 RedisModule_ReplyWithError(ctx, "UNEXPECTEDOK");
261 } else {
262 RedisModule_ReplyWithSimpleString(ctx, "OK");
263 }
264 return REDISMODULE_OK;
265}
266
267int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
268
269 if (RedisModule_Init(ctx,"aclcheck",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
270 return REDISMODULE_ERR;
271
272 if (argc > 1) return RedisModule_WrongArity(ctx);
273
274 /* When that flag is passed, we try to create too many categories,
275 * and the test expects this to fail. In this case redis returns REDISMODULE_ERR
276 * and set errno to ENOMEM*/
277 if (argc == 1) {
278 long long fail_flag = 0;
279 RedisModule_StringToLongLong(argv[0], &fail_flag);
280 if (fail_flag) {
281 for (size_t j = 0; j < 45; j++) {
282 RedisModuleString* name = RedisModule_CreateStringPrintf(ctx, "customcategory%zu", j);
283 if (RedisModule_AddACLCategory(ctx, RedisModule_StringPtrLen(name, NULL)) == REDISMODULE_ERR) {
284 RedisModule_Assert(errno == ENOMEM);
285 RedisModule_FreeString(ctx, name);
286 return REDISMODULE_ERR;
287 }
288 RedisModule_FreeString(ctx, name);
289 }
290 }
291 }
292
293 if (RedisModule_CreateCommand(ctx,"aclcheck.set.check.key", set_aclcheck_key,"write",0,0,0) == REDISMODULE_ERR)
294 return REDISMODULE_ERR;
295
296 if (RedisModule_CreateCommand(ctx,"aclcheck.set.check.prefixkey", set_aclcheck_prefixkey,"write",0,0,0) == REDISMODULE_ERR)
297 return REDISMODULE_ERR;
298
299 if (RedisModule_CreateCommand(ctx,"block.commands.outside.onload", commandBlockCheck,"write",0,0,0) == REDISMODULE_ERR)
300 return REDISMODULE_ERR;
301
302 if (RedisModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.write", module_test_acl_category,"write",0,0,0) == REDISMODULE_ERR)
303 return REDISMODULE_ERR;
304 RedisModuleCommand *aclcategories_write = RedisModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.write");
305
306 if (RedisModule_SetCommandACLCategories(aclcategories_write, "write") == REDISMODULE_ERR)
307 return REDISMODULE_ERR;
308
309 if (RedisModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.write.function.read.category", module_test_acl_category,"write",0,0,0) == REDISMODULE_ERR)
310 return REDISMODULE_ERR;
311 RedisModuleCommand *read_category = RedisModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.write.function.read.category");
312
313 if (RedisModule_SetCommandACLCategories(read_category, "read") == REDISMODULE_ERR)
314 return REDISMODULE_ERR;
315
316 if (RedisModule_CreateCommand(ctx,"aclcheck.module.command.aclcategories.read.only.category", module_test_acl_category,"",0,0,0) == REDISMODULE_ERR)
317 return REDISMODULE_ERR;
318 RedisModuleCommand *read_only_category = RedisModule_GetCommand(ctx,"aclcheck.module.command.aclcategories.read.only.category");
319
320 if (RedisModule_SetCommandACLCategories(read_only_category, "read") == REDISMODULE_ERR)
321 return REDISMODULE_ERR;
322
323 if (RedisModule_CreateCommand(ctx,"aclcheck.publish.check.channel", publish_aclcheck_channel,"",0,0,0) == REDISMODULE_ERR)
324 return REDISMODULE_ERR;
325
326 if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call.check.cmd", rm_call_aclcheck_cmd_default_user,"",0,0,0) == REDISMODULE_ERR)
327 return REDISMODULE_ERR;
328
329 if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call.check.cmd.module.user", rm_call_aclcheck_cmd_module_user,"",0,0,0) == REDISMODULE_ERR)
330 return REDISMODULE_ERR;
331
332 if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call", rm_call_aclcheck,
333 "write",0,0,0) == REDISMODULE_ERR)
334 return REDISMODULE_ERR;
335
336 if (RedisModule_CreateCommand(ctx,"aclcheck.rm_call_with_errors", rm_call_aclcheck_with_errors,
337 "write",0,0,0) == REDISMODULE_ERR)
338 return REDISMODULE_ERR;
339
340 /* This validates that, when module tries to add a category with invalid characters,
341 * redis returns REDISMODULE_ERR and set errno to `EINVAL` */
342 if (RedisModule_AddACLCategory(ctx,"!nval!dch@r@cter$") == REDISMODULE_ERR)
343 RedisModule_Assert(errno == EINVAL);
344 else
345 return REDISMODULE_ERR;
346
347 /* This validates that, when module tries to add a category that already exists,
348 * redis returns REDISMODULE_ERR and set errno to `EBUSY` */
349 if (RedisModule_AddACLCategory(ctx,"write") == REDISMODULE_ERR)
350 RedisModule_Assert(errno == EBUSY);
351 else
352 return REDISMODULE_ERR;
353
354 if (RedisModule_AddACLCategory(ctx,"foocategory") == REDISMODULE_ERR)
355 return REDISMODULE_ERR;
356
357 if (RedisModule_CreateCommand(ctx,"aclcheck.module.command.test.add.new.aclcategories", module_test_acl_category,"",0,0,0) == REDISMODULE_ERR)
358 return REDISMODULE_ERR;
359 RedisModuleCommand *test_add_new_aclcategories = RedisModule_GetCommand(ctx,"aclcheck.module.command.test.add.new.aclcategories");
360
361 if (RedisModule_SetCommandACLCategories(test_add_new_aclcategories, "foocategory") == REDISMODULE_ERR)
362 return REDISMODULE_ERR;
363
364 return REDISMODULE_OK;
365}
diff --git a/examples/redis-unstable/tests/modules/atomicslotmigration.c b/examples/redis-unstable/tests/modules/atomicslotmigration.c
new file mode 100644
index 0000000..83393cd
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/atomicslotmigration.c
@@ -0,0 +1,594 @@
1#include "redismodule.h"
2
3#include <stdlib.h>
4#include <memory.h>
5#include <errno.h>
6
7#define MAX_EVENTS 1024
8
9/* Log of cluster events. */
10const char *clusterEventLog[MAX_EVENTS];
11int numClusterEvents = 0;
12
13/* Log of cluster trim events. */
14const char *clusterTrimEventLog[MAX_EVENTS];
15int numClusterTrimEvents = 0;
16
17/* Log of last deleted key event. */
18const char *lastDeletedKeyLog = NULL;
19
20/* Flag to disable trim. */
21int disableTrimFlag = 0;
22
23int replicateModuleCommand = 0; /* Enable or disable module command replication. */
24RedisModuleString *moduleCommandKeyName = NULL; /* Key name to replicate. */
25RedisModuleString *moduleCommandKeyVal = NULL; /* Key value to replicate. */
26
27/* Enable or disable module command replication. */
28int replicate_module_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
29 if (argc != 4) {
30 RedisModule_ReplyWithError(ctx, "ERR wrong number of arguments");
31 return REDISMODULE_OK;
32 }
33
34 long long enable = 0;
35 if (RedisModule_StringToLongLong(argv[1], &enable) != REDISMODULE_OK) {
36 RedisModule_ReplyWithError(ctx, "ERR enable value");
37 return REDISMODULE_OK;
38 }
39 replicateModuleCommand = (enable != 0);
40
41 /* Set the key name and value to replicate. */
42 if (moduleCommandKeyName) RedisModule_FreeString(ctx, moduleCommandKeyName);
43 if (moduleCommandKeyVal) RedisModule_FreeString(ctx, moduleCommandKeyVal);
44 moduleCommandKeyName = RedisModule_CreateStringFromString(ctx, argv[2]);
45 moduleCommandKeyVal = RedisModule_CreateStringFromString(ctx, argv[3]);
46
47 RedisModule_ReplyWithSimpleString(ctx, "OK");
48 return REDISMODULE_OK;
49}
50
51int lpush_and_replicate_crossslot_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
52 if (argc != 3) return RedisModule_WrongArity(ctx);
53
54 /* LPUSH */
55 RedisModuleCallReply *rep = RedisModule_Call(ctx, "LPUSH", "!ss", argv[1], argv[2]);
56 RedisModule_Assert(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_ERROR);
57 RedisModule_FreeCallReply(rep);
58
59 /* Replicate cross slot command */
60 int ret = RedisModule_Replicate(ctx, "MSET", "cccccc", "key1", "val1", "key2", "val2", "key3", "val3");
61 RedisModule_Assert(ret == REDISMODULE_OK);
62
63 RedisModule_ReplyWithSimpleString(ctx, "OK");
64 return REDISMODULE_OK;
65}
66
67int testClusterGetLocalSlotRanges(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
68 REDISMODULE_NOT_USED(argv);
69 REDISMODULE_NOT_USED(argc);
70
71 static int use_auto_memory = 0;
72 use_auto_memory = !use_auto_memory;
73
74 RedisModuleSlotRangeArray *slots;
75 if (use_auto_memory) {
76 RedisModule_AutoMemory(ctx);
77 slots = RedisModule_ClusterGetLocalSlotRanges(ctx);
78 } else {
79 slots = RedisModule_ClusterGetLocalSlotRanges(NULL);
80 }
81
82 RedisModule_ReplyWithArray(ctx, slots->num_ranges);
83 for (int i = 0; i < slots->num_ranges; i++) {
84 RedisModule_ReplyWithArray(ctx, 2);
85 RedisModule_ReplyWithLongLong(ctx, slots->ranges[i].start);
86 RedisModule_ReplyWithLongLong(ctx, slots->ranges[i].end);
87 }
88 if (!use_auto_memory)
89 RedisModule_ClusterFreeSlotRanges(NULL, slots);
90 return REDISMODULE_OK;
91}
92
93/* Helper function to check if a slot range array contains a given slot. */
94int slotRangeArrayContains(RedisModuleSlotRangeArray *sra, unsigned int slot) {
95 for (int i = 0; i < sra->num_ranges; i++)
96 if (sra->ranges[i].start <= slot && sra->ranges[i].end >= slot)
97 return 1;
98 return 0;
99}
100
101/* Sanity check. */
102int sanity(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
103 REDISMODULE_NOT_USED(argv);
104 REDISMODULE_NOT_USED(argc);
105
106 RedisModule_Assert(RedisModule_ClusterCanAccessKeysInSlot(-1) == 0);
107 RedisModule_Assert(RedisModule_ClusterCanAccessKeysInSlot(16384) == 0);
108 RedisModule_Assert(RedisModule_ClusterCanAccessKeysInSlot(100000) == 0);
109
110 /* Call with invalid args. */
111 errno = 0;
112 RedisModule_Assert(RedisModule_ClusterPropagateForSlotMigration(NULL, NULL, NULL) == REDISMODULE_ERR);
113 RedisModule_Assert(errno == EINVAL);
114
115 /* Call with invalid args. */
116 errno = 0;
117 RedisModule_Assert(RedisModule_ClusterPropagateForSlotMigration(ctx, NULL, NULL) == REDISMODULE_ERR);
118 RedisModule_Assert(errno == EINVAL);
119
120 /* Call with invalid args. */
121 errno = 0;
122 RedisModule_Assert(RedisModule_ClusterPropagateForSlotMigration(NULL, "asm.keyless_cmd", "") == REDISMODULE_ERR);
123 RedisModule_Assert(errno == EINVAL);
124
125 /* Call outside of slot migration. */
126 errno = 0;
127 RedisModule_Assert(RedisModule_ClusterPropagateForSlotMigration(ctx, "asm.keyless_cmd", "") == REDISMODULE_ERR);
128 RedisModule_Assert(errno == EBADF);
129
130 RedisModule_ReplyWithSimpleString(ctx, "OK");
131 return REDISMODULE_OK;
132}
133
134/* Command to test RM_ClusterCanAccessKeysInSlot(). */
135int testClusterCanAccessKeysInSlot(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
136 REDISMODULE_NOT_USED(argc);
137 long long slot = 0;
138
139 if (RedisModule_StringToLongLong(argv[1],&slot) != REDISMODULE_OK) {
140 return RedisModule_ReplyWithError(ctx,"ERR invalid slot");
141 }
142 RedisModule_ReplyWithLongLong(ctx, RedisModule_ClusterCanAccessKeysInSlot(slot));
143 return REDISMODULE_OK;
144}
145
146/* Generate a string representation of the info struct and subevent.
147 e.g. 'sub: cluster-slot-migration-import-started, task_id: aeBd..., slots: 0-100,200-300' */
148const char *clusterAsmInfoToString(RedisModuleClusterSlotMigrationInfo *info, uint64_t sub) {
149 char buf[1024] = {0};
150
151 if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_IMPORT_STARTED)
152 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-import-started, ");
153 else if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_IMPORT_FAILED)
154 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-import-failed, ");
155 else if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_IMPORT_COMPLETED)
156 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-import-completed, ");
157 else if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_MIGRATE_STARTED)
158 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-migrate-started, ");
159 else if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_MIGRATE_FAILED)
160 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-migrate-failed, ");
161 else if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_MIGRATE_COMPLETED)
162 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-migrate-completed, ");
163 else {
164 RedisModule_Assert(0);
165 }
166 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "source_node_id:%.40s, destination_node_id:%.40s, ",
167 info->source_node_id, info->destination_node_id);
168 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "task_id:%s, slots:", info->task_id);
169 for (int i = 0; i < info->slots->num_ranges; i++) {
170 RedisModuleSlotRange *sr = &info->slots->ranges[i];
171 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%d-%d", sr->start, sr->end);
172 if (i != info->slots->num_ranges - 1)
173 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ",");
174 }
175 return RedisModule_Strdup(buf);
176}
177
178/* Generate a string representation of the info struct and subevent.
179 e.g. 'sub: cluster-slot-migration-trim-started, task_id: aeBd..., slots:0-100,200-300' */
180const char *clusterTrimInfoToString(RedisModuleClusterSlotMigrationTrimInfo *info, uint64_t sub) {
181 RedisModule_Assert(info);
182 char buf[1024] = {0};
183
184 if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_TRIM_BACKGROUND)
185 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-trim-background, ");
186 else if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_TRIM_STARTED)
187 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-trim-started, ");
188 else if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_TRIM_COMPLETED)
189 snprintf(buf, sizeof(buf), "sub: cluster-slot-migration-trim-completed, ");
190 else {
191 RedisModule_Assert(0);
192 }
193 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "slots:");
194 for (int i = 0; i < info->slots->num_ranges; i++) {
195 RedisModuleSlotRange *sr = &info->slots->ranges[i];
196 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%d-%d", sr->start, sr->end);
197 if (i != info->slots->num_ranges - 1)
198 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ",");
199 }
200 return RedisModule_Strdup(buf);
201}
202
203static void testReplicatingOutsideSlotRange(RedisModuleCtx *ctx, RedisModuleClusterSlotMigrationInfo *info) {
204 int slot = 0;
205 while (slot >= 0 && slot <= 16383) {
206 if (!slotRangeArrayContains(info->slots, slot)) {
207 break;
208 }
209 slot++;
210 }
211 char buf[128] = {0};
212 const char *prefix = RedisModule_ClusterCanonicalKeyNameInSlot(slot);
213 snprintf(buf, sizeof(buf), "{%s}%s", prefix, "modulekey");
214 errno = 0;
215 int ret = RedisModule_ClusterPropagateForSlotMigration(ctx, "SET", "cc", buf, "value");
216 RedisModule_Assert(ret == REDISMODULE_ERR);
217 RedisModule_Assert(errno == ERANGE);
218}
219
220static void testReplicatingCrossslotCommand(RedisModuleCtx *ctx) {
221 errno = 0;
222 int ret = RedisModule_ClusterPropagateForSlotMigration(ctx, "MSET", "cccccc", "key1", "val1", "key2", "val2", "key3", "val3");
223 RedisModule_Assert(ret == REDISMODULE_ERR);
224 RedisModule_Assert(errno == ENOTSUP);
225}
226
227static void testReplicatingUnknownCommand(RedisModuleCtx *ctx) {
228 errno = 0;
229 int ret = RedisModule_ClusterPropagateForSlotMigration(ctx, "unknowncommand", "");
230 RedisModule_Assert(ret == REDISMODULE_ERR);
231 RedisModule_Assert(errno == ENOENT);
232}
233
234static void testNonFatalScenarios(RedisModuleCtx *ctx, RedisModuleClusterSlotMigrationInfo *info) {
235 testReplicatingOutsideSlotRange(ctx, info);
236 testReplicatingCrossslotCommand(ctx);
237 testReplicatingUnknownCommand(ctx);
238}
239
240int disableTrimCmd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
241 REDISMODULE_NOT_USED(argv);
242 REDISMODULE_NOT_USED(argc);
243 disableTrimFlag = 1;
244 /* Only disable when MIGRATE_COMPLETED for simulating recommended usage. */
245 // RedisModule_ClusterDisableTrim(ctx)
246 RedisModule_ReplyWithSimpleString(ctx, "OK");
247 return REDISMODULE_OK;
248}
249
250int enableTrimCmd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
251 REDISMODULE_NOT_USED(argv);
252 REDISMODULE_NOT_USED(argc);
253 disableTrimFlag = 0;
254 RedisModule_Assert(RedisModule_ClusterEnableTrim(ctx) == REDISMODULE_OK);
255 RedisModule_ReplyWithSimpleString(ctx, "OK");
256 return REDISMODULE_OK;
257}
258
259int trimInProgressCmd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
260 REDISMODULE_NOT_USED(argv);
261 REDISMODULE_NOT_USED(argc);
262 uint64_t flags = RedisModule_GetContextFlags(ctx);
263 RedisModule_ReplyWithLongLong(ctx, !!(flags & REDISMODULE_CTX_FLAGS_TRIM_IN_PROGRESS));
264 return REDISMODULE_OK;
265}
266
267void clusterEventCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data) {
268 REDISMODULE_NOT_USED(ctx);
269 int ret;
270
271 RedisModule_Assert(RedisModule_IsSubEventSupported(e, sub));
272
273 if (e.id == REDISMODULE_EVENT_CLUSTER_SLOT_MIGRATION) {
274 RedisModuleClusterSlotMigrationInfo *info = data;
275
276 if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_MIGRATE_MODULE_PROPAGATE) {
277 /* Test some non-fatal scenarios. */
278 testNonFatalScenarios(ctx, info);
279
280 if (replicateModuleCommand == 0) return;
281
282 /* Replicate a keyless command. */
283 ret = RedisModule_ClusterPropagateForSlotMigration(ctx, "asm.keyless_cmd", "");
284 RedisModule_Assert(ret == REDISMODULE_OK);
285
286 /* Propagate configured key and value. */
287 ret = RedisModule_ClusterPropagateForSlotMigration(ctx, "SET", "ss", moduleCommandKeyName, moduleCommandKeyVal);
288 RedisModule_Assert(ret == REDISMODULE_OK);
289 } else {
290 /* Log the event. */
291 if (numClusterEvents >= MAX_EVENTS) return;
292 clusterEventLog[numClusterEvents++] = clusterAsmInfoToString(info, sub);
293
294 if (sub == REDISMODULE_SUBEVENT_CLUSTER_SLOT_MIGRATION_MIGRATE_COMPLETED) {
295 /* If users ask to disable trim, we disable trim. */
296 if (disableTrimFlag) {
297 RedisModule_Assert(RedisModule_ClusterDisableTrim(ctx) == REDISMODULE_OK);
298 }
299 }
300 }
301 }
302}
303
304int getPendingTrimKeyCmd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
305 if (argc != 2) {
306 RedisModule_ReplyWithError(ctx, "ERR wrong number of arguments");
307 return REDISMODULE_ERR;
308 }
309 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1],
310 REDISMODULE_READ | REDISMODULE_OPEN_KEY_ACCESS_TRIMMED);
311 if (!key) {
312 RedisModule_ReplyWithNull(ctx);
313 return REDISMODULE_OK;
314 }
315 if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_STRING) {
316 RedisModule_ReplyWithError(ctx, "key is not a string");
317 return REDISMODULE_ERR;
318 }
319 size_t len;
320 const char *value = RedisModule_StringDMA(key, &len, 0);
321 RedisModule_ReplyWithStringBuffer(ctx, value, len);
322 RedisModule_CloseKey(key);
323 return REDISMODULE_OK;
324}
325
326void clusterTrimEventCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data) {
327 REDISMODULE_NOT_USED(ctx);
328
329 RedisModule_Assert(RedisModule_IsSubEventSupported(e, sub));
330
331 if (e.id == REDISMODULE_EVENT_CLUSTER_SLOT_MIGRATION_TRIM) {
332 /* Log the event. */
333 if (numClusterTrimEvents >= MAX_EVENTS) return;
334 RedisModuleClusterSlotMigrationTrimInfo *info = data;
335 clusterTrimEventLog[numClusterTrimEvents++] = clusterTrimInfoToString(info, sub);
336 }
337}
338
339static int keyspaceNotificationTrimmedCallback(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
340 REDISMODULE_NOT_USED(ctx);
341
342 RedisModule_Assert(type == REDISMODULE_NOTIFY_KEY_TRIMMED);
343 RedisModule_Assert(strcmp(event, "key_trimmed") == 0);
344
345 if (numClusterTrimEvents >= MAX_EVENTS) return REDISMODULE_OK;
346
347 /* Log the trimmed key event. */
348 size_t len;
349 const char *key_str = RedisModule_StringPtrLen(key, &len);
350
351 char buf[1024] = {0};
352 snprintf(buf, sizeof(buf), "keyspace: key_trimmed, key: %s", key_str);
353
354 clusterTrimEventLog[numClusterTrimEvents++] = RedisModule_Strdup(buf);
355 return REDISMODULE_OK;
356}
357
358/* ASM.PARENT SET key value (just proxy to Redis SET) */
359static int asmParentSet(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
360 if (argc != 4) return RedisModule_WrongArity(ctx);
361 RedisModuleCallReply *reply = RedisModule_Call(ctx, "SET", "ss", argv[2], argv[3]);
362 if (!reply) return RedisModule_ReplyWithError(ctx, "ERR internal");
363 RedisModule_ReplyWithCallReply(ctx, reply);
364 RedisModule_FreeCallReply(reply);
365 RedisModule_ReplicateVerbatim(ctx);
366 return REDISMODULE_OK;
367}
368
369/* Clear both the cluster and trim event logs. */
370int clearEventLog(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
371 REDISMODULE_NOT_USED(argv);
372 REDISMODULE_NOT_USED(argc);
373
374 for (int i = 0; i < numClusterEvents; i++)
375 RedisModule_Free((void *)clusterEventLog[i]);
376 numClusterEvents = 0;
377
378 for (int i = 0; i < numClusterTrimEvents; i++)
379 RedisModule_Free((void *)clusterTrimEventLog[i]);
380 numClusterTrimEvents = 0;
381
382 RedisModule_ReplyWithSimpleString(ctx, "OK");
383 return REDISMODULE_OK;
384}
385
386/* Reply with the cluster event log. */
387int getClusterEventLog(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
388 REDISMODULE_NOT_USED(ctx);
389 REDISMODULE_NOT_USED(argv);
390 REDISMODULE_NOT_USED(argc);
391
392 RedisModule_ReplyWithArray(ctx, numClusterEvents);
393 for (int i = 0; i < numClusterEvents; i++)
394 RedisModule_ReplyWithStringBuffer(ctx, clusterEventLog[i], strlen(clusterEventLog[i]));
395 return REDISMODULE_OK;
396}
397
398/* Reply with the cluster trim event log. */
399int getClusterTrimEventLog(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
400 REDISMODULE_NOT_USED(ctx);
401 REDISMODULE_NOT_USED(argv);
402 REDISMODULE_NOT_USED(argc);
403
404 RedisModule_ReplyWithArray(ctx, numClusterTrimEvents);
405 for (int i = 0; i < numClusterTrimEvents; i++)
406 RedisModule_ReplyWithStringBuffer(ctx, clusterTrimEventLog[i], strlen(clusterTrimEventLog[i]));
407 return REDISMODULE_OK;
408}
409
410/* A keyless command to test module command replication. */
411int moduledata = 0;
412int keylessCmd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
413 REDISMODULE_NOT_USED(ctx);
414 REDISMODULE_NOT_USED(argv);
415 REDISMODULE_NOT_USED(argc);
416 moduledata++;
417 RedisModule_ReplyWithLongLong(ctx, moduledata);
418 return REDISMODULE_OK;
419}
420int readkeylessCmdVal(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
421 REDISMODULE_NOT_USED(ctx);
422 REDISMODULE_NOT_USED(argv);
423 REDISMODULE_NOT_USED(argc);
424 RedisModule_ReplyWithLongLong(ctx, moduledata);
425 return REDISMODULE_OK;
426}
427
428int subscribeTrimmedEvent(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
429 REDISMODULE_NOT_USED(ctx);
430 if (argc != 2)
431 return RedisModule_WrongArity(ctx);
432
433 long long subscribe = 0;
434 if (RedisModule_StringToLongLong(argv[1], &subscribe) != REDISMODULE_OK) {
435 RedisModule_ReplyWithError(ctx, "ERR subscribe value");
436 return REDISMODULE_OK;
437 }
438
439 if (subscribe) {
440 /* Unsubscribe first to avoid duplicate subscription. */
441 RedisModule_UnsubscribeFromKeyspaceEvents(ctx, REDISMODULE_NOTIFY_KEY_TRIMMED, keyspaceNotificationTrimmedCallback);
442 int ret = RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_KEY_TRIMMED, keyspaceNotificationTrimmedCallback);
443 RedisModule_Assert(ret == REDISMODULE_OK);
444 } else {
445 int ret = RedisModule_UnsubscribeFromKeyspaceEvents(ctx, REDISMODULE_NOTIFY_KEY_TRIMMED, keyspaceNotificationTrimmedCallback);
446 RedisModule_Assert(ret == REDISMODULE_OK);
447 }
448 RedisModule_ReplyWithSimpleString(ctx, "OK");
449 return REDISMODULE_OK;
450}
451
452void keyEventCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data) {
453 REDISMODULE_NOT_USED(ctx);
454 REDISMODULE_NOT_USED(e);
455
456 if (sub == REDISMODULE_SUBEVENT_KEY_DELETED) {
457 RedisModuleKeyInfoV1 *ei = data;
458 RedisModuleKey *kp = ei->key;
459 RedisModuleString *key = (RedisModuleString *) RedisModule_GetKeyNameFromModuleKey(kp);
460 size_t keylen;
461 const char *keyname = RedisModule_StringPtrLen(key, &keylen);
462
463 /* Verify value can be read. It will be used to verify key's value can
464 * be read in a trim callback. */
465 size_t valuelen = 0;
466 const char *value = "";
467 RedisModuleKey *mk = RedisModule_OpenKey(ctx, key, REDISMODULE_READ);
468 if (RedisModule_KeyType(mk) == REDISMODULE_KEYTYPE_STRING) {
469 value = RedisModule_StringDMA(mk, &valuelen, 0);
470 }
471 RedisModule_CloseKey(mk);
472
473 char buf[1024] = {0};
474 snprintf(buf, sizeof(buf), "keyevent: key: %.*s, value: %.*s", (int) keylen, keyname, (int)valuelen, value);
475
476 if (lastDeletedKeyLog) RedisModule_Free((void *)lastDeletedKeyLog);
477 lastDeletedKeyLog = RedisModule_Strdup(buf);
478 }
479}
480
481int getLastDeletedKey(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
482 REDISMODULE_NOT_USED(ctx);
483 REDISMODULE_NOT_USED(argv);
484 REDISMODULE_NOT_USED(argc);
485
486 if (lastDeletedKeyLog) {
487 RedisModule_ReplyWithStringBuffer(ctx, lastDeletedKeyLog, strlen(lastDeletedKeyLog));
488 } else {
489 RedisModule_ReplyWithNull(ctx);
490 }
491 return REDISMODULE_OK;
492}
493
494int asmGetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
495 REDISMODULE_NOT_USED(ctx);
496
497 if (argc != 2) return RedisModule_WrongArity(ctx);
498 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
499 if (key == NULL) {
500 RedisModule_ReplyWithNull(ctx);
501 return REDISMODULE_OK;
502 }
503
504 RedisModule_Assert(RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_STRING);
505 size_t len;
506 const char *value = RedisModule_StringDMA(key, &len, 0);
507 RedisModule_ReplyWithStringBuffer(ctx, value, len);
508 RedisModule_CloseKey(key);
509
510 return REDISMODULE_OK;
511}
512
513int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
514 REDISMODULE_NOT_USED(argv);
515 REDISMODULE_NOT_USED(argc);
516
517 if (RedisModule_Init(ctx, "asm", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
518 return REDISMODULE_ERR;
519
520 if (RedisModule_CreateCommand(ctx, "asm.cluster_can_access_keys_in_slot", testClusterCanAccessKeysInSlot, "", 0, 0, 0) == REDISMODULE_ERR)
521 return REDISMODULE_ERR;
522
523 if (RedisModule_CreateCommand(ctx, "asm.clear_event_log", clearEventLog, "", 0, 0, 0) == REDISMODULE_ERR)
524 return REDISMODULE_ERR;
525
526 if (RedisModule_CreateCommand(ctx, "asm.get_cluster_event_log", getClusterEventLog, "", 0, 0, 0) == REDISMODULE_ERR)
527 return REDISMODULE_ERR;
528
529 if (RedisModule_CreateCommand(ctx, "asm.get_cluster_trim_event_log", getClusterTrimEventLog, "", 0, 0, 0) == REDISMODULE_ERR)
530 return REDISMODULE_ERR;
531
532 if (RedisModule_CreateCommand(ctx, "asm.keyless_cmd", keylessCmd, "write", 0, 0, 0) == REDISMODULE_ERR)
533 return REDISMODULE_ERR;
534
535 if (RedisModule_CreateCommand(ctx, "asm.disable_trim", disableTrimCmd, "", 0, 0, 0) == REDISMODULE_ERR)
536 return REDISMODULE_ERR;
537
538 if (RedisModule_CreateCommand(ctx, "asm.enable_trim", enableTrimCmd, "", 0, 0, 0) == REDISMODULE_ERR)
539 return REDISMODULE_ERR;
540
541 if (RedisModule_CreateCommand(ctx, "asm.read_pending_trim_key", getPendingTrimKeyCmd, "readonly", 0, 0, 0) == REDISMODULE_ERR)
542 return REDISMODULE_ERR;
543
544 if (RedisModule_CreateCommand(ctx, "asm.trim_in_progress", trimInProgressCmd, "", 0, 0, 0) == REDISMODULE_ERR)
545 return REDISMODULE_ERR;
546
547 if (RedisModule_CreateCommand(ctx, "asm.read_keyless_cmd_val", readkeylessCmdVal, "", 0, 0, 0) == REDISMODULE_ERR)
548 return REDISMODULE_ERR;
549
550 if (RedisModule_CreateCommand(ctx, "asm.sanity", sanity, "", 0, 0, 0) == REDISMODULE_ERR)
551 return REDISMODULE_ERR;
552
553 if (RedisModule_CreateCommand(ctx, "asm.subscribe_trimmed_event", subscribeTrimmedEvent, "", 0, 0, 0) == REDISMODULE_ERR)
554 return REDISMODULE_ERR;
555
556 if (RedisModule_CreateCommand(ctx, "asm.replicate_module_command", replicate_module_command, "", 0, 0, 0) == REDISMODULE_ERR)
557 return REDISMODULE_ERR;
558
559 if (RedisModule_CreateCommand(ctx, "asm.lpush_replicate_crossslot_command", lpush_and_replicate_crossslot_command, "write", 0, 0, 0) == REDISMODULE_ERR)
560 return REDISMODULE_ERR;
561
562 if (RedisModule_CreateCommand(ctx, "asm.cluster_get_local_slot_ranges", testClusterGetLocalSlotRanges, "", 0, 0, 0) == REDISMODULE_ERR)
563 return REDISMODULE_ERR;
564
565 if (RedisModule_CreateCommand(ctx, "asm.get_last_deleted_key", getLastDeletedKey, "", 0, 0, 0) == REDISMODULE_ERR)
566 return REDISMODULE_ERR;
567
568 if (RedisModule_CreateCommand(ctx, "asm.get", asmGetCommand, "", 0, 0, 0) == REDISMODULE_ERR)
569 return REDISMODULE_ERR;
570
571 if (RedisModule_CreateCommand(ctx, "asm.parent", NULL, "", 0, 0, 0) == REDISMODULE_ERR)
572 return REDISMODULE_ERR;
573
574 RedisModuleCommand *parent = RedisModule_GetCommand(ctx, "asm.parent");
575 if (!parent) return REDISMODULE_ERR;
576
577 /* Subcommand: ASM.PARENT SET (write) */
578 if (RedisModule_CreateSubcommand(parent, "set", asmParentSet, "write fast", 2, 2, 1) == REDISMODULE_ERR)
579 return REDISMODULE_ERR;
580
581 if (RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_ClusterSlotMigration, clusterEventCallback) == REDISMODULE_ERR)
582 return REDISMODULE_ERR;
583
584 if (RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_ClusterSlotMigrationTrim, clusterTrimEventCallback) == REDISMODULE_ERR)
585 return REDISMODULE_ERR;
586
587 if (RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_KEY_TRIMMED, keyspaceNotificationTrimmedCallback) == REDISMODULE_ERR)
588 return REDISMODULE_ERR;
589
590 if (RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_Key, keyEventCallback) == REDISMODULE_ERR)
591 return REDISMODULE_ERR;
592
593 return REDISMODULE_OK;
594}
diff --git a/examples/redis-unstable/tests/modules/auth.c b/examples/redis-unstable/tests/modules/auth.c
new file mode 100644
index 0000000..cc2378e
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/auth.c
@@ -0,0 +1,286 @@
1/* define macros for having usleep */
2#define _BSD_SOURCE
3#define _DEFAULT_SOURCE
4
5#include "redismodule.h"
6
7#include <string.h>
8#include <unistd.h>
9#include <pthread.h>
10
11#define UNUSED(V) ((void) V)
12
13// A simple global user
14static RedisModuleUser *global = NULL;
15static long long client_change_delta = 0;
16static pthread_t tid;
17
18void UserChangedCallback(uint64_t client_id, void *privdata) {
19 REDISMODULE_NOT_USED(privdata);
20 REDISMODULE_NOT_USED(client_id);
21 client_change_delta++;
22}
23
24int Auth_CreateModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
25 REDISMODULE_NOT_USED(argv);
26 REDISMODULE_NOT_USED(argc);
27
28 if (global) {
29 RedisModule_FreeModuleUser(global);
30 }
31
32 global = RedisModule_CreateModuleUser("global");
33 RedisModule_SetModuleUserACL(global, "allcommands");
34 RedisModule_SetModuleUserACL(global, "allkeys");
35 RedisModule_SetModuleUserACL(global, "on");
36
37 return RedisModule_ReplyWithSimpleString(ctx, "OK");
38}
39
40int Auth_AuthModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
41 REDISMODULE_NOT_USED(argv);
42 REDISMODULE_NOT_USED(argc);
43 uint64_t client_id;
44 RedisModule_AuthenticateClientWithUser(ctx, global, UserChangedCallback, NULL, &client_id);
45
46 return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id);
47}
48
49int Auth_AuthRealUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
50 if (argc != 2) return RedisModule_WrongArity(ctx);
51
52 size_t length;
53 uint64_t client_id;
54
55 RedisModuleString *user_string = argv[1];
56 const char *name = RedisModule_StringPtrLen(user_string, &length);
57
58 if (RedisModule_AuthenticateClientWithACLUser(ctx, name, length,
59 UserChangedCallback, NULL, &client_id) == REDISMODULE_ERR) {
60 return RedisModule_ReplyWithError(ctx, "Invalid user");
61 }
62
63 return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id);
64}
65
66/* This command redacts every other arguments and returns OK */
67int Auth_RedactedAPI(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
68 REDISMODULE_NOT_USED(argv);
69 for(int i = argc - 1; i > 0; i -= 2) {
70 int result = RedisModule_RedactClientCommandArgument(ctx, i);
71 RedisModule_Assert(result == REDISMODULE_OK);
72 }
73 return RedisModule_ReplyWithSimpleString(ctx, "OK");
74}
75
76int Auth_ChangeCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
77 REDISMODULE_NOT_USED(argv);
78 REDISMODULE_NOT_USED(argc);
79 long long result = client_change_delta;
80 client_change_delta = 0;
81 return RedisModule_ReplyWithLongLong(ctx, result);
82}
83
84/* The Module functionality below validates that module authentication callbacks can be registered
85 * to support both non-blocking and blocking module based authentication. */
86
87/* Non Blocking Module Auth callback / implementation. */
88int auth_cb(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err) {
89 const char *user = RedisModule_StringPtrLen(username, NULL);
90 const char *pwd = RedisModule_StringPtrLen(password, NULL);
91 if (!strcmp(user,"foo") && !strcmp(pwd,"allow")) {
92 RedisModule_AuthenticateClientWithACLUser(ctx, "foo", 3, NULL, NULL, NULL);
93 return REDISMODULE_AUTH_HANDLED;
94 }
95 else if (!strcmp(user,"foo") && !strcmp(pwd,"deny")) {
96 RedisModuleString *log = RedisModule_CreateString(ctx, "Module Auth", 11);
97 RedisModule_ACLAddLogEntryByUserName(ctx, username, log, REDISMODULE_ACL_LOG_AUTH);
98 RedisModule_FreeString(ctx, log);
99 const char *err_msg = "Auth denied by Misc Module.";
100 *err = RedisModule_CreateString(ctx, err_msg, strlen(err_msg));
101 return REDISMODULE_AUTH_HANDLED;
102 }
103 return REDISMODULE_AUTH_NOT_HANDLED;
104}
105
106int test_rm_register_auth_cb(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
107 REDISMODULE_NOT_USED(argv);
108 REDISMODULE_NOT_USED(argc);
109 RedisModule_RegisterAuthCallback(ctx, auth_cb);
110 RedisModule_ReplyWithSimpleString(ctx, "OK");
111 return REDISMODULE_OK;
112}
113
114/*
115 * The thread entry point that actually executes the blocking part of the AUTH command.
116 * This function sleeps for 0.5 seconds and then unblocks the client which will later call
117 * `AuthBlock_Reply`.
118 * `arg` is expected to contain the RedisModuleBlockedClient, username, and password.
119 */
120void *AuthBlock_ThreadMain(void *arg) {
121 usleep(500000);
122 void **targ = arg;
123 RedisModuleBlockedClient *bc = targ[0];
124 int result = 2;
125 const char *user = RedisModule_StringPtrLen(targ[1], NULL);
126 const char *pwd = RedisModule_StringPtrLen(targ[2], NULL);
127 if (!strcmp(user,"foo") && !strcmp(pwd,"block_allow")) {
128 result = 1;
129 }
130 else if (!strcmp(user,"foo") && !strcmp(pwd,"block_deny")) {
131 result = 0;
132 }
133 else if (!strcmp(user,"foo") && !strcmp(pwd,"block_abort")) {
134 RedisModule_BlockedClientMeasureTimeEnd(bc);
135 RedisModule_AbortBlock(bc);
136 goto cleanup;
137 }
138 /* Provide the result to the blocking reply cb. */
139 void **replyarg = RedisModule_Alloc(sizeof(void*));
140 replyarg[0] = (void *) (uintptr_t) result;
141 RedisModule_BlockedClientMeasureTimeEnd(bc);
142 RedisModule_UnblockClient(bc, replyarg);
143cleanup:
144 /* Free the username and password and thread / arg data. */
145 RedisModule_FreeString(NULL, targ[1]);
146 RedisModule_FreeString(NULL, targ[2]);
147 RedisModule_Free(targ);
148 return NULL;
149}
150
151/*
152 * Reply callback for a blocking AUTH command. This is called when the client is unblocked.
153 */
154int AuthBlock_Reply(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err) {
155 REDISMODULE_NOT_USED(password);
156 void **targ = RedisModule_GetBlockedClientPrivateData(ctx);
157 int result = (uintptr_t) targ[0];
158 size_t userlen = 0;
159 const char *user = RedisModule_StringPtrLen(username, &userlen);
160 /* Handle the success case by authenticating. */
161 if (result == 1) {
162 RedisModule_AuthenticateClientWithACLUser(ctx, user, userlen, NULL, NULL, NULL);
163 return REDISMODULE_AUTH_HANDLED;
164 }
165 /* Handle the Error case by denying auth */
166 else if (result == 0) {
167 RedisModuleString *log = RedisModule_CreateString(ctx, "Module Auth", 11);
168 RedisModule_ACLAddLogEntryByUserName(ctx, username, log, REDISMODULE_ACL_LOG_AUTH);
169 RedisModule_FreeString(ctx, log);
170 const char *err_msg = "Auth denied by Misc Module.";
171 *err = RedisModule_CreateString(ctx, err_msg, strlen(err_msg));
172 return REDISMODULE_AUTH_HANDLED;
173 }
174 /* "Skip" Authentication */
175 return REDISMODULE_AUTH_NOT_HANDLED;
176}
177
178/* Private data freeing callback for Module Auth. */
179void AuthBlock_FreeData(RedisModuleCtx *ctx, void *privdata) {
180 REDISMODULE_NOT_USED(ctx);
181 RedisModule_Free(privdata);
182}
183
184/* Callback triggered when the engine attempts module auth
185 * Return code here is one of the following: Auth succeeded, Auth denied,
186 * Auth not handled, Auth blocked.
187 * The Module can have auth succeed / denied here itself, but this is an example
188 * of blocking module auth.
189 */
190int blocking_auth_cb(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err) {
191 REDISMODULE_NOT_USED(username);
192 REDISMODULE_NOT_USED(password);
193 REDISMODULE_NOT_USED(err);
194 /* Block the client from the Module. */
195 RedisModuleBlockedClient *bc = RedisModule_BlockClientOnAuth(ctx, AuthBlock_Reply, AuthBlock_FreeData);
196 int ctx_flags = RedisModule_GetContextFlags(ctx);
197 if (ctx_flags & REDISMODULE_CTX_FLAGS_MULTI || ctx_flags & REDISMODULE_CTX_FLAGS_LUA) {
198 /* Clean up by using RedisModule_UnblockClient since we attempted blocking the client. */
199 RedisModule_UnblockClient(bc, NULL);
200 return REDISMODULE_AUTH_HANDLED;
201 }
202
203 /* Another blocking auth cb may have spawned a thread, we'll just wait for it
204 * to finish here */
205 if (tid) pthread_join(tid, NULL);
206
207 RedisModule_BlockedClientMeasureTimeStart(bc);
208
209 /* Allocate memory for information needed. */
210 void **targ = RedisModule_Alloc(sizeof(void*)*3);
211 targ[0] = bc;
212 targ[1] = RedisModule_CreateStringFromString(NULL, username);
213 targ[2] = RedisModule_CreateStringFromString(NULL, password);
214
215 /* Create bg thread and pass the blockedclient, username and password to it. */
216 if (pthread_create(&tid, NULL, AuthBlock_ThreadMain, targ) != 0) {
217 RedisModule_AbortBlock(bc);
218
219 /* These are freed in AuthBlock_ThreadMain but since we failed to spawn
220 * the thread need to free them here. */
221 RedisModule_FreeString(NULL, targ[1]);
222 RedisModule_FreeString(NULL, targ[2]);
223 RedisModule_Free(targ);
224 }
225
226 return REDISMODULE_AUTH_HANDLED;
227}
228
229int test_rm_register_blocking_auth_cb(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
230 REDISMODULE_NOT_USED(argv);
231 REDISMODULE_NOT_USED(argc);
232 RedisModule_RegisterAuthCallback(ctx, blocking_auth_cb);
233 RedisModule_ReplyWithSimpleString(ctx, "OK");
234 return REDISMODULE_OK;
235}
236
237/* This function must be present on each Redis module. It is used in order to
238 * register the commands into the Redis server. */
239int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
240 REDISMODULE_NOT_USED(argv);
241 REDISMODULE_NOT_USED(argc);
242
243 if (RedisModule_Init(ctx,"testacl",1,REDISMODULE_APIVER_1)
244 == REDISMODULE_ERR) return REDISMODULE_ERR;
245
246 if (RedisModule_CreateCommand(ctx,"auth.authrealuser",
247 Auth_AuthRealUser,"no-auth",0,0,0) == REDISMODULE_ERR)
248 return REDISMODULE_ERR;
249
250 if (RedisModule_CreateCommand(ctx,"auth.createmoduleuser",
251 Auth_CreateModuleUser,"",0,0,0) == REDISMODULE_ERR)
252 return REDISMODULE_ERR;
253
254 if (RedisModule_CreateCommand(ctx,"auth.authmoduleuser",
255 Auth_AuthModuleUser,"no-auth",0,0,0) == REDISMODULE_ERR)
256 return REDISMODULE_ERR;
257
258 if (RedisModule_CreateCommand(ctx,"auth.changecount",
259 Auth_ChangeCount,"",0,0,0) == REDISMODULE_ERR)
260 return REDISMODULE_ERR;
261
262 if (RedisModule_CreateCommand(ctx,"auth.redact",
263 Auth_RedactedAPI,"",0,0,0) == REDISMODULE_ERR)
264 return REDISMODULE_ERR;
265
266 if (RedisModule_CreateCommand(ctx,"testmoduleone.rm_register_auth_cb",
267 test_rm_register_auth_cb,"",0,0,0) == REDISMODULE_ERR)
268 return REDISMODULE_ERR;
269
270 if (RedisModule_CreateCommand(ctx,"testmoduleone.rm_register_blocking_auth_cb",
271 test_rm_register_blocking_auth_cb,"",0,0,0) == REDISMODULE_ERR)
272 return REDISMODULE_ERR;
273
274 return REDISMODULE_OK;
275}
276
277int RedisModule_OnUnload(RedisModuleCtx *ctx) {
278 UNUSED(ctx);
279
280 if (tid) pthread_join(tid, NULL);
281
282 if (global)
283 RedisModule_FreeModuleUser(global);
284
285 return REDISMODULE_OK;
286}
diff --git a/examples/redis-unstable/tests/modules/basics.c b/examples/redis-unstable/tests/modules/basics.c
new file mode 100644
index 0000000..85c6196
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/basics.c
@@ -0,0 +1,1051 @@
1/* Module designed to test the Redis modules subsystem.
2 *
3 * -----------------------------------------------------------------------------
4 *
5 * Copyright (c) 2016-Present, Redis Ltd.
6 * All rights reserved.
7 *
8 * Licensed under your choice of (a) the Redis Source Available License 2.0
9 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
10 * GNU Affero General Public License v3 (AGPLv3).
11 */
12
13#include "redismodule.h"
14#include <string.h>
15#include <stdlib.h>
16
17/* --------------------------------- Helpers -------------------------------- */
18
19/* Return true if the reply and the C null term string matches. */
20int TestMatchReply(RedisModuleCallReply *reply, char *str) {
21 RedisModuleString *mystr;
22 mystr = RedisModule_CreateStringFromCallReply(reply);
23 if (!mystr) return 0;
24 const char *ptr = RedisModule_StringPtrLen(mystr,NULL);
25 return strcmp(ptr,str) == 0;
26}
27
28/* ------------------------------- Test units ------------------------------- */
29
30/* TEST.CALL -- Test Call() API. */
31int TestCall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
32 REDISMODULE_NOT_USED(argv);
33 REDISMODULE_NOT_USED(argc);
34
35 RedisModule_AutoMemory(ctx);
36 RedisModuleCallReply *reply;
37
38 RedisModule_Call(ctx,"DEL","c","mylist");
39 RedisModuleString *mystr = RedisModule_CreateString(ctx,"foo",3);
40 RedisModule_Call(ctx,"RPUSH","csl","mylist",mystr,(long long)1234);
41 reply = RedisModule_Call(ctx,"LRANGE","ccc","mylist","0","-1");
42 long long items = RedisModule_CallReplyLength(reply);
43 if (items != 2) goto fail;
44
45 RedisModuleCallReply *item0, *item1;
46
47 item0 = RedisModule_CallReplyArrayElement(reply,0);
48 item1 = RedisModule_CallReplyArrayElement(reply,1);
49 if (!TestMatchReply(item0,"foo")) goto fail;
50 if (!TestMatchReply(item1,"1234")) goto fail;
51
52 RedisModule_ReplyWithSimpleString(ctx,"OK");
53 return REDISMODULE_OK;
54
55fail:
56 RedisModule_ReplyWithSimpleString(ctx,"ERR");
57 return REDISMODULE_OK;
58}
59
60int TestCallResp3Attribute(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
61 REDISMODULE_NOT_USED(argv);
62 REDISMODULE_NOT_USED(argc);
63
64 RedisModule_AutoMemory(ctx);
65 RedisModuleCallReply *reply;
66
67 reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "attrib"); /* 3 stands for resp 3 reply */
68 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) goto fail;
69
70 /* make sure we can not reply to resp2 client with resp3 (it might be a string but it contains attribute) */
71 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
72
73 if (!TestMatchReply(reply,"Some real reply following the attribute")) goto fail;
74
75 reply = RedisModule_CallReplyAttribute(reply);
76 if (!reply || RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ATTRIBUTE) goto fail;
77 /* make sure we can not reply to resp2 client with resp3 attribute */
78 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
79 if (RedisModule_CallReplyLength(reply) != 1) goto fail;
80
81 RedisModuleCallReply *key, *val;
82 if (RedisModule_CallReplyAttributeElement(reply,0,&key,&val) != REDISMODULE_OK) goto fail;
83 if (!TestMatchReply(key,"key-popularity")) goto fail;
84 if (RedisModule_CallReplyType(val) != REDISMODULE_REPLY_ARRAY) goto fail;
85 if (RedisModule_CallReplyLength(val) != 2) goto fail;
86 if (!TestMatchReply(RedisModule_CallReplyArrayElement(val, 0),"key:123")) goto fail;
87 if (!TestMatchReply(RedisModule_CallReplyArrayElement(val, 1),"90")) goto fail;
88
89 RedisModule_ReplyWithSimpleString(ctx,"OK");
90 return REDISMODULE_OK;
91
92fail:
93 RedisModule_ReplyWithSimpleString(ctx,"ERR");
94 return REDISMODULE_OK;
95}
96
97int TestGetResp(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
98 REDISMODULE_NOT_USED(argv);
99 REDISMODULE_NOT_USED(argc);
100
101 int flags = RedisModule_GetContextFlags(ctx);
102
103 if (flags & REDISMODULE_CTX_FLAGS_RESP3) {
104 RedisModule_ReplyWithLongLong(ctx, 3);
105 } else {
106 RedisModule_ReplyWithLongLong(ctx, 2);
107 }
108
109 return REDISMODULE_OK;
110}
111
112int TestCallRespAutoMode(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
113 REDISMODULE_NOT_USED(argv);
114 REDISMODULE_NOT_USED(argc);
115
116 RedisModule_AutoMemory(ctx);
117 RedisModuleCallReply *reply;
118
119 RedisModule_Call(ctx,"DEL","c","myhash");
120 RedisModule_Call(ctx,"HSET","ccccc","myhash", "f1", "v1", "f2", "v2");
121 /* 0 stands for auto mode, we will get the reply in the same format as the client */
122 reply = RedisModule_Call(ctx,"HGETALL","0c" ,"myhash");
123 RedisModule_ReplyWithCallReply(ctx, reply);
124 return REDISMODULE_OK;
125}
126
127int TestCallResp3Map(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
128 REDISMODULE_NOT_USED(argv);
129 REDISMODULE_NOT_USED(argc);
130
131 RedisModule_AutoMemory(ctx);
132 RedisModuleCallReply *reply;
133
134 RedisModule_Call(ctx,"DEL","c","myhash");
135 RedisModule_Call(ctx,"HSET","ccccc","myhash", "f1", "v1", "f2", "v2");
136 reply = RedisModule_Call(ctx,"HGETALL","3c" ,"myhash"); /* 3 stands for resp 3 reply */
137 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_MAP) goto fail;
138
139 /* make sure we can not reply to resp2 client with resp3 map */
140 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
141
142 long long items = RedisModule_CallReplyLength(reply);
143 if (items != 2) goto fail;
144
145 RedisModuleCallReply *key0, *key1;
146 RedisModuleCallReply *val0, *val1;
147 if (RedisModule_CallReplyMapElement(reply,0,&key0,&val0) != REDISMODULE_OK) goto fail;
148 if (RedisModule_CallReplyMapElement(reply,1,&key1,&val1) != REDISMODULE_OK) goto fail;
149 if (!TestMatchReply(key0,"f1")) goto fail;
150 if (!TestMatchReply(key1,"f2")) goto fail;
151 if (!TestMatchReply(val0,"v1")) goto fail;
152 if (!TestMatchReply(val1,"v2")) goto fail;
153
154 RedisModule_ReplyWithSimpleString(ctx,"OK");
155 return REDISMODULE_OK;
156
157fail:
158 RedisModule_ReplyWithSimpleString(ctx,"ERR");
159 return REDISMODULE_OK;
160}
161
162int TestCallResp3Bool(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
163 REDISMODULE_NOT_USED(argv);
164 REDISMODULE_NOT_USED(argc);
165
166 RedisModule_AutoMemory(ctx);
167 RedisModuleCallReply *reply;
168
169 reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "true"); /* 3 stands for resp 3 reply */
170 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_BOOL) goto fail;
171 /* make sure we can not reply to resp2 client with resp3 bool */
172 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
173
174 if (!RedisModule_CallReplyBool(reply)) goto fail;
175 reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "false"); /* 3 stands for resp 3 reply */
176 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_BOOL) goto fail;
177 if (RedisModule_CallReplyBool(reply)) goto fail;
178
179 RedisModule_ReplyWithSimpleString(ctx,"OK");
180 return REDISMODULE_OK;
181
182fail:
183 RedisModule_ReplyWithSimpleString(ctx,"ERR");
184 return REDISMODULE_OK;
185}
186
187int TestCallResp3Null(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
188 REDISMODULE_NOT_USED(argv);
189 REDISMODULE_NOT_USED(argc);
190
191 RedisModule_AutoMemory(ctx);
192 RedisModuleCallReply *reply;
193
194 reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "null"); /* 3 stands for resp 3 reply */
195 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_NULL) goto fail;
196
197 /* make sure we can not reply to resp2 client with resp3 null */
198 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
199
200 RedisModule_ReplyWithSimpleString(ctx,"OK");
201 return REDISMODULE_OK;
202
203fail:
204 RedisModule_ReplyWithSimpleString(ctx,"ERR");
205 return REDISMODULE_OK;
206}
207
208int TestCallReplyWithNestedReply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
209 REDISMODULE_NOT_USED(argv);
210 REDISMODULE_NOT_USED(argc);
211
212 RedisModule_AutoMemory(ctx);
213 RedisModuleCallReply *reply;
214
215 RedisModule_Call(ctx,"DEL","c","mylist");
216 RedisModule_Call(ctx,"RPUSH","ccl","mylist","test",(long long)1234);
217 reply = RedisModule_Call(ctx,"LRANGE","ccc","mylist","0","-1");
218 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ARRAY) goto fail;
219 if (RedisModule_CallReplyLength(reply) < 1) goto fail;
220 RedisModuleCallReply *nestedReply = RedisModule_CallReplyArrayElement(reply, 0);
221
222 RedisModule_ReplyWithCallReply(ctx,nestedReply);
223 return REDISMODULE_OK;
224
225fail:
226 RedisModule_ReplyWithSimpleString(ctx,"ERR");
227 return REDISMODULE_OK;
228}
229
230int TestCallReplyWithArrayReply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
231 REDISMODULE_NOT_USED(argv);
232 REDISMODULE_NOT_USED(argc);
233
234 RedisModule_AutoMemory(ctx);
235 RedisModuleCallReply *reply;
236
237 RedisModule_Call(ctx,"DEL","c","mylist");
238 RedisModule_Call(ctx,"RPUSH","ccl","mylist","test",(long long)1234);
239 reply = RedisModule_Call(ctx,"LRANGE","ccc","mylist","0","-1");
240 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ARRAY) goto fail;
241
242 RedisModule_ReplyWithCallReply(ctx,reply);
243 return REDISMODULE_OK;
244
245fail:
246 RedisModule_ReplyWithSimpleString(ctx,"ERR");
247 return REDISMODULE_OK;
248}
249
250int TestCallResp3Double(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
251 REDISMODULE_NOT_USED(argv);
252 REDISMODULE_NOT_USED(argc);
253
254 RedisModule_AutoMemory(ctx);
255 RedisModuleCallReply *reply;
256
257 reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "double"); /* 3 stands for resp 3 reply */
258 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_DOUBLE) goto fail;
259
260 /* make sure we can not reply to resp2 client with resp3 double*/
261 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
262
263 double d = RedisModule_CallReplyDouble(reply);
264 /* we compare strings, since comparing doubles directly can fail in various architectures, e.g. 32bit */
265 char got[30], expected[30];
266 snprintf(got, sizeof(got), "%.17g", d);
267 snprintf(expected, sizeof(expected), "%.17g", 3.141);
268 if (strcmp(got, expected) != 0) goto fail;
269 RedisModule_ReplyWithSimpleString(ctx,"OK");
270 return REDISMODULE_OK;
271
272fail:
273 RedisModule_ReplyWithSimpleString(ctx,"ERR");
274 return REDISMODULE_OK;
275}
276
277int TestCallResp3BigNumber(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
278 REDISMODULE_NOT_USED(argv);
279 REDISMODULE_NOT_USED(argc);
280
281 RedisModule_AutoMemory(ctx);
282 RedisModuleCallReply *reply;
283
284 reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "bignum"); /* 3 stands for resp 3 reply */
285 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_BIG_NUMBER) goto fail;
286
287 /* make sure we can not reply to resp2 client with resp3 big number */
288 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
289
290 size_t len;
291 const char* big_num = RedisModule_CallReplyBigNumber(reply, &len);
292 RedisModule_ReplyWithStringBuffer(ctx,big_num,len);
293 return REDISMODULE_OK;
294
295fail:
296 RedisModule_ReplyWithSimpleString(ctx,"ERR");
297 return REDISMODULE_OK;
298}
299
300int TestCallResp3Verbatim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
301 REDISMODULE_NOT_USED(argv);
302 REDISMODULE_NOT_USED(argc);
303
304 RedisModule_AutoMemory(ctx);
305 RedisModuleCallReply *reply;
306
307 reply = RedisModule_Call(ctx,"DEBUG","3cc" ,"PROTOCOL", "verbatim"); /* 3 stands for resp 3 reply */
308 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_VERBATIM_STRING) goto fail;
309
310 /* make sure we can not reply to resp2 client with resp3 verbatim string */
311 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
312
313 const char* format;
314 size_t len;
315 const char* str = RedisModule_CallReplyVerbatim(reply, &len, &format);
316 RedisModuleString *s = RedisModule_CreateStringPrintf(ctx, "%.*s:%.*s", 3, format, (int)len, str);
317 RedisModule_ReplyWithString(ctx,s);
318 return REDISMODULE_OK;
319
320fail:
321 RedisModule_ReplyWithSimpleString(ctx,"ERR");
322 return REDISMODULE_OK;
323}
324
325int TestCallResp3Set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
326 REDISMODULE_NOT_USED(argv);
327 REDISMODULE_NOT_USED(argc);
328
329 RedisModule_AutoMemory(ctx);
330 RedisModuleCallReply *reply;
331
332 RedisModule_Call(ctx,"DEL","c","myset");
333 RedisModule_Call(ctx,"sadd","ccc","myset", "v1", "v2");
334 reply = RedisModule_Call(ctx,"smembers","3c" ,"myset"); // N stands for resp 3 reply
335 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_SET) goto fail;
336
337 /* make sure we can not reply to resp2 client with resp3 set */
338 if (RedisModule_ReplyWithCallReply(ctx, reply) != REDISMODULE_ERR) goto fail;
339
340 long long items = RedisModule_CallReplyLength(reply);
341 if (items != 2) goto fail;
342
343 RedisModuleCallReply *val0, *val1;
344
345 val0 = RedisModule_CallReplySetElement(reply,0);
346 val1 = RedisModule_CallReplySetElement(reply,1);
347
348 /*
349 * The order of elements on sets are not promised so we just
350 * veridy that the reply matches one of the elements.
351 */
352 if (!TestMatchReply(val0,"v1") && !TestMatchReply(val0,"v2")) goto fail;
353 if (!TestMatchReply(val1,"v1") && !TestMatchReply(val1,"v2")) goto fail;
354
355 RedisModule_ReplyWithSimpleString(ctx,"OK");
356 return REDISMODULE_OK;
357
358fail:
359 RedisModule_ReplyWithSimpleString(ctx,"ERR");
360 return REDISMODULE_OK;
361}
362
363/* TEST.STRING.APPEND -- Test appending to an existing string object. */
364int TestStringAppend(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
365 REDISMODULE_NOT_USED(argv);
366 REDISMODULE_NOT_USED(argc);
367
368 RedisModuleString *s = RedisModule_CreateString(ctx,"foo",3);
369 RedisModule_StringAppendBuffer(ctx,s,"bar",3);
370 RedisModule_ReplyWithString(ctx,s);
371 RedisModule_FreeString(ctx,s);
372 return REDISMODULE_OK;
373}
374
375/* TEST.STRING.APPEND.AM -- Test append with retain when auto memory is on. */
376int TestStringAppendAM(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
377 REDISMODULE_NOT_USED(argv);
378 REDISMODULE_NOT_USED(argc);
379
380 RedisModule_AutoMemory(ctx);
381 RedisModuleString *s = RedisModule_CreateString(ctx,"foo",3);
382 RedisModule_RetainString(ctx,s);
383 RedisModule_TrimStringAllocation(s); /* Mostly NOP, but exercises the API function */
384 RedisModule_StringAppendBuffer(ctx,s,"bar",3);
385 RedisModule_ReplyWithString(ctx,s);
386 RedisModule_FreeString(ctx,s);
387 return REDISMODULE_OK;
388}
389
390/* TEST.STRING.TRIM -- Test we trim a string with free space. */
391int TestTrimString(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
392 REDISMODULE_NOT_USED(argv);
393 REDISMODULE_NOT_USED(argc);
394 RedisModuleString *s = RedisModule_CreateString(ctx,"foo",3);
395 char *tmp = RedisModule_Alloc(1024);
396 RedisModule_StringAppendBuffer(ctx,s,tmp,1024);
397 size_t string_len = RedisModule_MallocSizeString(s);
398 RedisModule_TrimStringAllocation(s);
399 size_t len_after_trim = RedisModule_MallocSizeString(s);
400
401 /* Determine if using jemalloc memory allocator. */
402 RedisModuleServerInfoData *info = RedisModule_GetServerInfo(ctx, "memory");
403 const char *field = RedisModule_ServerInfoGetFieldC(info, "mem_allocator");
404 int use_jemalloc = !strncmp(field, "jemalloc", 8);
405
406 /* Jemalloc will reallocate `s` from 2k to 1k after RedisModule_TrimStringAllocation(),
407 * but non-jemalloc memory allocators may keep the old size. */
408 if ((use_jemalloc && len_after_trim < string_len) ||
409 (!use_jemalloc && len_after_trim <= string_len))
410 {
411 RedisModule_ReplyWithSimpleString(ctx, "OK");
412 } else {
413 RedisModule_ReplyWithError(ctx, "String was not trimmed as expected.");
414 }
415 RedisModule_FreeServerInfo(ctx, info);
416 RedisModule_Free(tmp);
417 RedisModule_FreeString(ctx,s);
418 return REDISMODULE_OK;
419}
420
421/* TEST.STRING.PRINTF -- Test string formatting. */
422int TestStringPrintf(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
423 RedisModule_AutoMemory(ctx);
424 if (argc < 3) {
425 return RedisModule_WrongArity(ctx);
426 }
427 RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
428 "Got %d args. argv[1]: %s, argv[2]: %s",
429 argc,
430 RedisModule_StringPtrLen(argv[1], NULL),
431 RedisModule_StringPtrLen(argv[2], NULL)
432 );
433
434 RedisModule_ReplyWithString(ctx,s);
435
436 return REDISMODULE_OK;
437}
438
439int failTest(RedisModuleCtx *ctx, const char *msg) {
440 RedisModule_ReplyWithError(ctx, msg);
441 return REDISMODULE_ERR;
442}
443
444int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
445 RedisModule_AutoMemory(ctx);
446 REDISMODULE_NOT_USED(argv);
447 REDISMODULE_NOT_USED(argc);
448
449 RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "unlinked"), REDISMODULE_WRITE | REDISMODULE_READ);
450 if (!k) return failTest(ctx, "Could not create key");
451
452 if (REDISMODULE_ERR == RedisModule_StringSet(k, RedisModule_CreateStringPrintf(ctx, "Foobar"))) {
453 return failTest(ctx, "Could not set string value");
454 }
455
456 RedisModuleCallReply *rep = RedisModule_Call(ctx, "EXISTS", "c", "unlinked");
457 if (!rep || RedisModule_CallReplyInteger(rep) != 1) {
458 return failTest(ctx, "Key does not exist before unlink");
459 }
460
461 if (REDISMODULE_ERR == RedisModule_UnlinkKey(k)) {
462 return failTest(ctx, "Could not unlink key");
463 }
464
465 rep = RedisModule_Call(ctx, "EXISTS", "c", "unlinked");
466 if (!rep || RedisModule_CallReplyInteger(rep) != 0) {
467 return failTest(ctx, "Could not verify key to be unlinked");
468 }
469 return RedisModule_ReplyWithSimpleString(ctx, "OK");
470}
471
472int TestNestedCallReplyArrayElement(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
473 RedisModule_AutoMemory(ctx);
474 REDISMODULE_NOT_USED(argv);
475 REDISMODULE_NOT_USED(argc);
476
477 RedisModuleString *expect_key = RedisModule_CreateString(ctx, "mykey", strlen("mykey"));
478 RedisModule_SelectDb(ctx, 1);
479 RedisModule_Call(ctx, "LPUSH", "sc", expect_key, "myvalue");
480
481 RedisModuleCallReply *scan_reply = RedisModule_Call(ctx, "SCAN", "l", (long long)0);
482 RedisModule_Assert(scan_reply != NULL && RedisModule_CallReplyType(scan_reply) == REDISMODULE_REPLY_ARRAY);
483 RedisModule_Assert(RedisModule_CallReplyLength(scan_reply) == 2);
484
485 long long scan_cursor;
486 RedisModuleCallReply *cursor_reply = RedisModule_CallReplyArrayElement(scan_reply, 0);
487 RedisModule_Assert(RedisModule_CallReplyType(cursor_reply) == REDISMODULE_REPLY_STRING);
488 RedisModule_Assert(RedisModule_StringToLongLong(RedisModule_CreateStringFromCallReply(cursor_reply), &scan_cursor) == REDISMODULE_OK);
489 RedisModule_Assert(scan_cursor == 0);
490
491 RedisModuleCallReply *keys_reply = RedisModule_CallReplyArrayElement(scan_reply, 1);
492 RedisModule_Assert(RedisModule_CallReplyType(keys_reply) == REDISMODULE_REPLY_ARRAY);
493 RedisModule_Assert( RedisModule_CallReplyLength(keys_reply) == 1);
494
495 RedisModuleCallReply *key_reply = RedisModule_CallReplyArrayElement(keys_reply, 0);
496 RedisModule_Assert(RedisModule_CallReplyType(key_reply) == REDISMODULE_REPLY_STRING);
497 RedisModuleString *key = RedisModule_CreateStringFromCallReply(key_reply);
498 RedisModule_Assert(RedisModule_StringCompare(key, expect_key) == 0);
499
500 RedisModule_ReplyWithSimpleString(ctx, "OK");
501 return REDISMODULE_OK;
502}
503
504/* TEST.STRING.TRUNCATE -- Test truncating an existing string object. */
505int TestStringTruncate(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
506 RedisModule_AutoMemory(ctx);
507 REDISMODULE_NOT_USED(argv);
508 REDISMODULE_NOT_USED(argc);
509
510 RedisModule_Call(ctx, "SET", "cc", "foo", "abcde");
511 RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "foo"), REDISMODULE_READ | REDISMODULE_WRITE);
512 if (!k) return failTest(ctx, "Could not create key");
513
514 size_t len = 0;
515 char* s;
516
517 /* expand from 5 to 8 and check null pad */
518 if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 8)) {
519 return failTest(ctx, "Could not truncate string value (8)");
520 }
521 s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
522 if (!s) {
523 return failTest(ctx, "Failed to read truncated string (8)");
524 } else if (len != 8) {
525 return failTest(ctx, "Failed to expand string value (8)");
526 } else if (0 != strncmp(s, "abcde\0\0\0", 8)) {
527 return failTest(ctx, "Failed to null pad string value (8)");
528 }
529
530 /* shrink from 8 to 4 */
531 if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 4)) {
532 return failTest(ctx, "Could not truncate string value (4)");
533 }
534 s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
535 if (!s) {
536 return failTest(ctx, "Failed to read truncated string (4)");
537 } else if (len != 4) {
538 return failTest(ctx, "Failed to shrink string value (4)");
539 } else if (0 != strncmp(s, "abcd", 4)) {
540 return failTest(ctx, "Failed to truncate string value (4)");
541 }
542
543 /* shrink to 0 */
544 if (REDISMODULE_ERR == RedisModule_StringTruncate(k, 0)) {
545 return failTest(ctx, "Could not truncate string value (0)");
546 }
547 s = RedisModule_StringDMA(k, &len, REDISMODULE_READ);
548 if (!s) {
549 return failTest(ctx, "Failed to read truncated string (0)");
550 } else if (len != 0) {
551 return failTest(ctx, "Failed to shrink string value to (0)");
552 }
553
554 return RedisModule_ReplyWithSimpleString(ctx, "OK");
555}
556
557int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
558 RedisModuleString *key) {
559 RedisModule_AutoMemory(ctx);
560 /* Increment a counter on the notifications: for each key notified we
561 * increment a counter */
562 RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s", type,
563 event, RedisModule_StringPtrLen(key, NULL));
564
565 RedisModule_Call(ctx, "HINCRBY", "csc", "notifications", key, "1");
566 return REDISMODULE_OK;
567}
568
569/* TEST.NOTIFICATIONS -- Test Keyspace Notifications. */
570int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
571 RedisModule_AutoMemory(ctx);
572 REDISMODULE_NOT_USED(argv);
573 REDISMODULE_NOT_USED(argc);
574
575#define FAIL(msg, ...) \
576 { \
577 RedisModule_Log(ctx, "warning", "Failed NOTIFY Test. Reason: " #msg, ##__VA_ARGS__); \
578 goto err; \
579 }
580 RedisModule_Call(ctx, "FLUSHDB", "");
581
582 RedisModule_Call(ctx, "SET", "cc", "foo", "bar");
583 RedisModule_Call(ctx, "SET", "cc", "foo", "baz");
584 RedisModule_Call(ctx, "SADD", "cc", "bar", "x");
585 RedisModule_Call(ctx, "SADD", "cc", "bar", "y");
586
587 RedisModule_Call(ctx, "HSET", "ccc", "baz", "x", "y");
588 /* LPUSH should be ignored and not increment any counters */
589 RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
590 RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
591
592 /* Miss some keys intentionally so we will get a "keymiss" notification. */
593 RedisModule_Call(ctx, "GET", "c", "nosuchkey");
594 RedisModule_Call(ctx, "SMEMBERS", "c", "nosuchkey");
595
596 size_t sz;
597 const char *rep;
598 RedisModuleCallReply *r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo");
599 if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
600 FAIL("Wrong or no reply for foo");
601 } else {
602 rep = RedisModule_CallReplyStringPtr(r, &sz);
603 if (sz != 1 || *rep != '2') {
604 FAIL("Got reply '%s'. expected '2'", RedisModule_CallReplyStringPtr(r, NULL));
605 }
606 }
607
608 r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "bar");
609 if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
610 FAIL("Wrong or no reply for bar");
611 } else {
612 rep = RedisModule_CallReplyStringPtr(r, &sz);
613 if (sz != 1 || *rep != '2') {
614 FAIL("Got reply '%s'. expected '2'", rep);
615 }
616 }
617
618 r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "baz");
619 if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
620 FAIL("Wrong or no reply for baz");
621 } else {
622 rep = RedisModule_CallReplyStringPtr(r, &sz);
623 if (sz != 1 || *rep != '1') {
624 FAIL("Got reply '%.*s'. expected '1'", (int)sz, rep);
625 }
626 }
627 /* For l we expect nothing since we didn't subscribe to list events */
628 r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "l");
629 if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_NULL) {
630 FAIL("Wrong reply for l");
631 }
632
633 r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "nosuchkey");
634 if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
635 FAIL("Wrong or no reply for nosuchkey");
636 } else {
637 rep = RedisModule_CallReplyStringPtr(r, &sz);
638 if (sz != 1 || *rep != '2') {
639 FAIL("Got reply '%.*s'. expected '2'", (int)sz, rep);
640 }
641 }
642
643 RedisModule_Call(ctx, "FLUSHDB", "");
644
645 return RedisModule_ReplyWithSimpleString(ctx, "OK");
646err:
647 RedisModule_Call(ctx, "FLUSHDB", "");
648
649 return RedisModule_ReplyWithSimpleString(ctx, "ERR");
650}
651
652/* TEST.CTXFLAGS -- Test GetContextFlags. */
653int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
654 REDISMODULE_NOT_USED(argc);
655 REDISMODULE_NOT_USED(argv);
656
657 RedisModule_AutoMemory(ctx);
658
659 int ok = 1;
660 const char *errString = NULL;
661#undef FAIL
662#define FAIL(msg) \
663 { \
664 ok = 0; \
665 errString = msg; \
666 goto end; \
667 }
668
669 int flags = RedisModule_GetContextFlags(ctx);
670 if (flags == 0) {
671 FAIL("Got no flags");
672 }
673
674 if (flags & REDISMODULE_CTX_FLAGS_LUA) FAIL("Lua flag was set");
675 if (flags & REDISMODULE_CTX_FLAGS_MULTI) FAIL("Multi flag was set");
676
677 if (flags & REDISMODULE_CTX_FLAGS_AOF) FAIL("AOF Flag was set")
678 /* Enable AOF to test AOF flags */
679 RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "yes");
680 flags = RedisModule_GetContextFlags(ctx);
681 if (!(flags & REDISMODULE_CTX_FLAGS_AOF)) FAIL("AOF Flag not set after config set");
682
683 /* Disable RDB saving and test the flag. */
684 RedisModule_Call(ctx, "config", "ccc", "set", "save", "");
685 flags = RedisModule_GetContextFlags(ctx);
686 if (flags & REDISMODULE_CTX_FLAGS_RDB) FAIL("RDB Flag was set");
687 /* Enable RDB to test RDB flags */
688 RedisModule_Call(ctx, "config", "ccc", "set", "save", "900 1");
689 flags = RedisModule_GetContextFlags(ctx);
690 if (!(flags & REDISMODULE_CTX_FLAGS_RDB)) FAIL("RDB Flag was not set after config set");
691
692 if (!(flags & REDISMODULE_CTX_FLAGS_MASTER)) FAIL("Master flag was not set");
693 if (flags & REDISMODULE_CTX_FLAGS_SLAVE) FAIL("Slave flag was set");
694 if (flags & REDISMODULE_CTX_FLAGS_READONLY) FAIL("Read-only flag was set");
695 if (flags & REDISMODULE_CTX_FLAGS_CLUSTER) FAIL("Cluster flag was set");
696
697 /* Disable maxmemory and test the flag. (it is implicitly set in 32bit builds. */
698 RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0");
699 flags = RedisModule_GetContextFlags(ctx);
700 if (flags & REDISMODULE_CTX_FLAGS_MAXMEMORY) FAIL("Maxmemory flag was set");
701
702 /* Enable maxmemory and test the flag. */
703 RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "100000000");
704 flags = RedisModule_GetContextFlags(ctx);
705 if (!(flags & REDISMODULE_CTX_FLAGS_MAXMEMORY))
706 FAIL("Maxmemory flag was not set after config set");
707
708 if (flags & REDISMODULE_CTX_FLAGS_EVICT) FAIL("Eviction flag was set");
709 RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "allkeys-lru");
710 flags = RedisModule_GetContextFlags(ctx);
711 if (!(flags & REDISMODULE_CTX_FLAGS_EVICT)) FAIL("Eviction flag was not set after config set");
712
713end:
714 /* Revert config changes */
715 RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "no");
716 RedisModule_Call(ctx, "config", "ccc", "set", "save", "");
717 RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0");
718 RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "noeviction");
719
720 if (!ok) {
721 RedisModule_Log(ctx, "warning", "Failed CTXFLAGS Test. Reason: %s", errString);
722 return RedisModule_ReplyWithSimpleString(ctx, "ERR");
723 }
724
725 return RedisModule_ReplyWithSimpleString(ctx, "OK");
726}
727
728/* ----------------------------- Test framework ----------------------------- */
729
730/* Return 1 if the reply matches the specified string, otherwise log errors
731 * in the server log and return 0. */
732int TestAssertErrorReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) {
733 RedisModuleString *mystr, *expected;
734 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ERROR) {
735 return 0;
736 }
737
738 mystr = RedisModule_CreateStringFromCallReply(reply);
739 expected = RedisModule_CreateString(ctx,str,len);
740 if (RedisModule_StringCompare(mystr,expected) != 0) {
741 const char *mystr_ptr = RedisModule_StringPtrLen(mystr,NULL);
742 const char *expected_ptr = RedisModule_StringPtrLen(expected,NULL);
743 RedisModule_Log(ctx,"warning",
744 "Unexpected Error reply reply '%s' (instead of '%s')",
745 mystr_ptr, expected_ptr);
746 return 0;
747 }
748 return 1;
749}
750
751int TestAssertStringReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, char *str, size_t len) {
752 RedisModuleString *mystr, *expected;
753
754 if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) {
755 RedisModule_Log(ctx,"warning","Test error reply: %s",
756 RedisModule_CallReplyStringPtr(reply, NULL));
757 return 0;
758 } else if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) {
759 RedisModule_Log(ctx,"warning","Unexpected reply type %d",
760 RedisModule_CallReplyType(reply));
761 return 0;
762 }
763 mystr = RedisModule_CreateStringFromCallReply(reply);
764 expected = RedisModule_CreateString(ctx,str,len);
765 if (RedisModule_StringCompare(mystr,expected) != 0) {
766 const char *mystr_ptr = RedisModule_StringPtrLen(mystr,NULL);
767 const char *expected_ptr = RedisModule_StringPtrLen(expected,NULL);
768 RedisModule_Log(ctx,"warning",
769 "Unexpected string reply '%s' (instead of '%s')",
770 mystr_ptr, expected_ptr);
771 return 0;
772 }
773 return 1;
774}
775
776/* Return 1 if the reply matches the specified integer, otherwise log errors
777 * in the server log and return 0. */
778int TestAssertIntegerReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, long long expected) {
779 if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_ERROR) {
780 RedisModule_Log(ctx,"warning","Test error reply: %s",
781 RedisModule_CallReplyStringPtr(reply, NULL));
782 return 0;
783 } else if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_INTEGER) {
784 RedisModule_Log(ctx,"warning","Unexpected reply type %d",
785 RedisModule_CallReplyType(reply));
786 return 0;
787 }
788 long long val = RedisModule_CallReplyInteger(reply);
789 if (val != expected) {
790 RedisModule_Log(ctx,"warning",
791 "Unexpected integer reply '%lld' (instead of '%lld')",
792 val, expected);
793 return 0;
794 }
795 return 1;
796}
797
798/* Replies "yes", "no" otherwise if the context may execute debug commands */
799int TestCanDebug(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
800 REDISMODULE_NOT_USED(argv);
801 REDISMODULE_NOT_USED(argc);
802 int flags = RedisModule_GetContextFlags(ctx);
803 int allFlags = RedisModule_GetContextFlagsAll();
804 if ((allFlags & REDISMODULE_CTX_FLAGS_DEBUG_ENABLED) &&
805 (flags & REDISMODULE_CTX_FLAGS_DEBUG_ENABLED)) {
806 RedisModule_ReplyWithSimpleString(ctx, "yes");
807 } else {
808 RedisModule_ReplyWithSimpleString(ctx, "no");
809 }
810 return REDISMODULE_OK;
811}
812
813#define T(name,...) \
814 do { \
815 RedisModule_Log(ctx,"warning","Testing %s", name); \
816 reply = RedisModule_Call(ctx,name,__VA_ARGS__); \
817 } while (0)
818
819/* TEST.BASICS -- Run all the tests.
820 * Note: it is useful to run these tests from the module rather than TCL
821 * since it's easier to check the reply types like that make a distinction
822 * between 0 and "0", etc. */
823int TestBasics(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
824 REDISMODULE_NOT_USED(argv);
825 REDISMODULE_NOT_USED(argc);
826
827 RedisModule_AutoMemory(ctx);
828 RedisModuleCallReply *reply;
829
830 /* Make sure the DB is empty before to proceed. */
831 T("dbsize","");
832 if (!TestAssertIntegerReply(ctx,reply,0)) goto fail;
833
834 T("ping","");
835 if (!TestAssertStringReply(ctx,reply,"PONG",4)) goto fail;
836
837 T("test.call","");
838 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
839
840 T("test.callresp3map","");
841 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
842
843 T("test.callresp3set","");
844 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
845
846 T("test.callresp3double","");
847 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
848
849 T("test.callresp3bool","");
850 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
851
852 T("test.callresp3null","");
853 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
854
855 T("test.callreplywithnestedreply","");
856 if (!TestAssertStringReply(ctx,reply,"test",4)) goto fail;
857
858 T("test.callreplywithbignumberreply","");
859 if (!TestAssertStringReply(ctx,reply,"1234567999999999999999999999999999999",37)) goto fail;
860
861 T("test.callreplywithverbatimstringreply","");
862 if (!TestAssertStringReply(ctx,reply,"txt:This is a verbatim\nstring",29)) goto fail;
863
864 T("test.ctxflags","");
865 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
866
867 T("test.string.append","");
868 if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail;
869
870 T("test.string.truncate","");
871 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
872
873 T("test.unlink","");
874 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
875
876 T("test.nestedcallreplyarray","");
877 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
878
879 T("test.string.append.am","");
880 if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail;
881
882 T("test.string.trim","");
883 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
884
885 T("test.string.printf", "cc", "foo", "bar");
886 if (!TestAssertStringReply(ctx,reply,"Got 3 args. argv[1]: foo, argv[2]: bar",38)) goto fail;
887
888 T("test.notify", "");
889 if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
890
891 T("test.callreplywitharrayreply", "");
892 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_ARRAY) goto fail;
893 if (RedisModule_CallReplyLength(reply) != 2) goto fail;
894 if (!TestAssertStringReply(ctx,RedisModule_CallReplyArrayElement(reply, 0),"test",4)) goto fail;
895 if (!TestAssertStringReply(ctx,RedisModule_CallReplyArrayElement(reply, 1),"1234",4)) goto fail;
896
897 T("foo", "E");
898 if (!TestAssertErrorReply(ctx,reply,"ERR unknown command 'foo'",25)) goto fail;
899
900 T("set", "Ec", "x");
901 if (!TestAssertErrorReply(ctx,reply,"ERR wrong number of arguments for 'set' command",47)) goto fail;
902
903 T("shutdown", "SE");
904 if (!TestAssertErrorReply(ctx,reply,"ERR command 'shutdown' is not allowed on script mode",52)) goto fail;
905
906 T("set", "WEcc", "x", "1");
907 if (!TestAssertErrorReply(ctx,reply,"ERR Write command 'set' was called while write is not allowed.",62)) goto fail;
908
909 RedisModule_ReplyWithSimpleString(ctx,"ALL TESTS PASSED");
910 return REDISMODULE_OK;
911
912fail:
913 RedisModule_ReplyWithSimpleString(ctx,
914 "SOME TEST DID NOT PASS! Check server logs");
915 return REDISMODULE_OK;
916}
917
918int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
919 REDISMODULE_NOT_USED(argv);
920 REDISMODULE_NOT_USED(argc);
921
922 if (RedisModule_Init(ctx,"test",1,REDISMODULE_APIVER_1)
923 == REDISMODULE_ERR) return REDISMODULE_ERR;
924
925 /* Perform RM_Call inside the RedisModule_OnLoad
926 * to verify that it works as expected without crashing.
927 * The tests will verify it on different configurations
928 * options (cluster/no cluster). A simple ping command
929 * is enough for this test. */
930 RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "");
931 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) {
932 RedisModule_FreeCallReply(reply);
933 return REDISMODULE_ERR;
934 }
935 size_t len;
936 const char *reply_str = RedisModule_CallReplyStringPtr(reply, &len);
937 if (len != 4) {
938 RedisModule_FreeCallReply(reply);
939 return REDISMODULE_ERR;
940 }
941 if (memcmp(reply_str, "PONG", 4) != 0) {
942 RedisModule_FreeCallReply(reply);
943 return REDISMODULE_ERR;
944 }
945 RedisModule_FreeCallReply(reply);
946
947 if (RedisModule_CreateCommand(ctx,"test.call",
948 TestCall,"write deny-oom",1,1,1) == REDISMODULE_ERR)
949 return REDISMODULE_ERR;
950
951 if (RedisModule_CreateCommand(ctx,"test.callresp3map",
952 TestCallResp3Map,"write deny-oom",1,1,1) == REDISMODULE_ERR)
953 return REDISMODULE_ERR;
954
955 if (RedisModule_CreateCommand(ctx,"test.callresp3attribute",
956 TestCallResp3Attribute,"write deny-oom",1,1,1) == REDISMODULE_ERR)
957 return REDISMODULE_ERR;
958
959 if (RedisModule_CreateCommand(ctx,"test.callresp3set",
960 TestCallResp3Set,"write deny-oom",1,1,1) == REDISMODULE_ERR)
961 return REDISMODULE_ERR;
962
963 if (RedisModule_CreateCommand(ctx,"test.callresp3double",
964 TestCallResp3Double,"write deny-oom",1,1,1) == REDISMODULE_ERR)
965 return REDISMODULE_ERR;
966
967 if (RedisModule_CreateCommand(ctx,"test.callresp3bool",
968 TestCallResp3Bool,"write deny-oom",1,1,1) == REDISMODULE_ERR)
969 return REDISMODULE_ERR;
970
971 if (RedisModule_CreateCommand(ctx,"test.callresp3null",
972 TestCallResp3Null,"write deny-oom",1,1,1) == REDISMODULE_ERR)
973 return REDISMODULE_ERR;
974
975 if (RedisModule_CreateCommand(ctx,"test.callreplywitharrayreply",
976 TestCallReplyWithArrayReply,"write deny-oom",1,1,1) == REDISMODULE_ERR)
977 return REDISMODULE_ERR;
978
979 if (RedisModule_CreateCommand(ctx,"test.callreplywithnestedreply",
980 TestCallReplyWithNestedReply,"write deny-oom",1,1,1) == REDISMODULE_ERR)
981 return REDISMODULE_ERR;
982
983 if (RedisModule_CreateCommand(ctx,"test.callreplywithbignumberreply",
984 TestCallResp3BigNumber,"write deny-oom",1,1,1) == REDISMODULE_ERR)
985 return REDISMODULE_ERR;
986
987 if (RedisModule_CreateCommand(ctx,"test.callreplywithverbatimstringreply",
988 TestCallResp3Verbatim,"write deny-oom",1,1,1) == REDISMODULE_ERR)
989 return REDISMODULE_ERR;
990
991 if (RedisModule_CreateCommand(ctx,"test.string.append",
992 TestStringAppend,"write deny-oom",1,1,1) == REDISMODULE_ERR)
993 return REDISMODULE_ERR;
994
995 if (RedisModule_CreateCommand(ctx,"test.string.trim",
996 TestTrimString,"write deny-oom",1,1,1) == REDISMODULE_ERR)
997 return REDISMODULE_ERR;
998
999 if (RedisModule_CreateCommand(ctx,"test.string.append.am",
1000 TestStringAppendAM,"write deny-oom",1,1,1) == REDISMODULE_ERR)
1001 return REDISMODULE_ERR;
1002
1003 if (RedisModule_CreateCommand(ctx,"test.string.truncate",
1004 TestStringTruncate,"write deny-oom",1,1,1) == REDISMODULE_ERR)
1005 return REDISMODULE_ERR;
1006
1007 if (RedisModule_CreateCommand(ctx,"test.string.printf",
1008 TestStringPrintf,"write deny-oom",1,1,1) == REDISMODULE_ERR)
1009 return REDISMODULE_ERR;
1010
1011 if (RedisModule_CreateCommand(ctx,"test.ctxflags",
1012 TestCtxFlags,"readonly",1,1,1) == REDISMODULE_ERR)
1013 return REDISMODULE_ERR;
1014
1015 if (RedisModule_CreateCommand(ctx,"test.unlink",
1016 TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR)
1017 return REDISMODULE_ERR;
1018
1019 if (RedisModule_CreateCommand(ctx,"test.nestedcallreplyarray",
1020 TestNestedCallReplyArrayElement,"write deny-oom",1,1,1) == REDISMODULE_ERR)
1021 return REDISMODULE_ERR;
1022
1023 if (RedisModule_CreateCommand(ctx,"test.basics",
1024 TestBasics,"write",1,1,1) == REDISMODULE_ERR)
1025 return REDISMODULE_ERR;
1026
1027 /* the following commands are used by an external test and should not be added to TestBasics */
1028 if (RedisModule_CreateCommand(ctx,"test.rmcallautomode",
1029 TestCallRespAutoMode,"write",1,1,1) == REDISMODULE_ERR)
1030 return REDISMODULE_ERR;
1031
1032 if (RedisModule_CreateCommand(ctx,"test.getresp",
1033 TestGetResp,"readonly",1,1,1) == REDISMODULE_ERR)
1034 return REDISMODULE_ERR;
1035
1036 if (RedisModule_CreateCommand(ctx,"test.candebug",
1037 TestCanDebug,"readonly",1,1,1) == REDISMODULE_ERR)
1038 return REDISMODULE_ERR;
1039
1040 RedisModule_SubscribeToKeyspaceEvents(ctx,
1041 REDISMODULE_NOTIFY_HASH |
1042 REDISMODULE_NOTIFY_SET |
1043 REDISMODULE_NOTIFY_STRING |
1044 REDISMODULE_NOTIFY_KEY_MISS,
1045 NotifyCallback);
1046 if (RedisModule_CreateCommand(ctx,"test.notify",
1047 TestNotifications,"write deny-oom",1,1,1) == REDISMODULE_ERR)
1048 return REDISMODULE_ERR;
1049
1050 return REDISMODULE_OK;
1051}
diff --git a/examples/redis-unstable/tests/modules/blockedclient.c b/examples/redis-unstable/tests/modules/blockedclient.c
new file mode 100644
index 0000000..dc226ee
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/blockedclient.c
@@ -0,0 +1,723 @@
1/* define macros for having usleep */
2#define _BSD_SOURCE
3#define _DEFAULT_SOURCE
4#include <unistd.h>
5
6#include "redismodule.h"
7#include <assert.h>
8#include <stdio.h>
9#include <pthread.h>
10#include <strings.h>
11
12#define UNUSED(V) ((void) V)
13
14/* used to test processing events during slow bg operation */
15static volatile int g_slow_bg_operation = 0;
16static volatile int g_is_in_slow_bg_operation = 0;
17
18void *sub_worker(void *arg) {
19 // Get Redis module context
20 RedisModuleCtx *ctx = (RedisModuleCtx *)arg;
21
22 // Try acquiring GIL
23 int res = RedisModule_ThreadSafeContextTryLock(ctx);
24
25 // GIL is already taken by the calling thread expecting to fail.
26 assert(res != REDISMODULE_OK);
27
28 return NULL;
29}
30
31void *worker(void *arg) {
32 // Retrieve blocked client
33 RedisModuleBlockedClient *bc = (RedisModuleBlockedClient *)arg;
34
35 // Get Redis module context
36 RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bc);
37
38 // Acquire GIL
39 RedisModule_ThreadSafeContextLock(ctx);
40
41 // Create another thread which will try to acquire the GIL
42 pthread_t tid;
43 int res = pthread_create(&tid, NULL, sub_worker, ctx);
44 assert(res == 0);
45
46 // Wait for thread
47 pthread_join(tid, NULL);
48
49 // Release GIL
50 RedisModule_ThreadSafeContextUnlock(ctx);
51
52 // Reply to client
53 RedisModule_ReplyWithSimpleString(ctx, "OK");
54
55 // Unblock client
56 RedisModule_UnblockClient(bc, NULL);
57
58 // Free the Redis module context
59 RedisModule_FreeThreadSafeContext(ctx);
60
61 return NULL;
62}
63
64int acquire_gil(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
65{
66 UNUSED(argv);
67 UNUSED(argc);
68
69 int flags = RedisModule_GetContextFlags(ctx);
70 int allFlags = RedisModule_GetContextFlagsAll();
71 if ((allFlags & REDISMODULE_CTX_FLAGS_MULTI) &&
72 (flags & REDISMODULE_CTX_FLAGS_MULTI)) {
73 RedisModule_ReplyWithSimpleString(ctx, "Blocked client is not supported inside multi");
74 return REDISMODULE_OK;
75 }
76
77 if ((allFlags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING) &&
78 (flags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING)) {
79 RedisModule_ReplyWithSimpleString(ctx, "Blocked client is not allowed");
80 return REDISMODULE_OK;
81 }
82
83 /* This command handler tries to acquire the GIL twice
84 * once in the worker thread using "RedisModule_ThreadSafeContextLock"
85 * second in the sub-worker thread
86 * using "RedisModule_ThreadSafeContextTryLock"
87 * as the GIL is already locked. */
88 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
89
90 pthread_t tid;
91 int res = pthread_create(&tid, NULL, worker, bc);
92 assert(res == 0);
93 pthread_detach(tid);
94
95 return REDISMODULE_OK;
96}
97
98typedef struct {
99 RedisModuleString **argv;
100 int argc;
101 RedisModuleBlockedClient *bc;
102} bg_call_data;
103
104void *bg_call_worker(void *arg) {
105 bg_call_data *bg = arg;
106 RedisModuleBlockedClient *bc = bg->bc;
107
108 // Get Redis module context
109 RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bg->bc);
110
111 // Acquire GIL
112 RedisModule_ThreadSafeContextLock(ctx);
113
114 // Test slow operation yielding
115 if (g_slow_bg_operation) {
116 g_is_in_slow_bg_operation = 1;
117 while (g_slow_bg_operation) {
118 RedisModule_Yield(ctx, REDISMODULE_YIELD_FLAG_CLIENTS, "Slow module operation");
119 usleep(1000);
120 }
121 g_is_in_slow_bg_operation = 0;
122 }
123
124 // Call the command
125 const char *module_cmd = RedisModule_StringPtrLen(bg->argv[0], NULL);
126 int cmd_pos = 1;
127 RedisModuleString *format_redis_str = RedisModule_CreateString(NULL, "v", 1);
128 if (!strcasecmp(module_cmd, "do_bg_rm_call_format")) {
129 cmd_pos = 2;
130 size_t format_len;
131 const char *format = RedisModule_StringPtrLen(bg->argv[1], &format_len);
132 RedisModule_StringAppendBuffer(NULL, format_redis_str, format, format_len);
133 RedisModule_StringAppendBuffer(NULL, format_redis_str, "E", 1);
134 }
135 const char *format = RedisModule_StringPtrLen(format_redis_str, NULL);
136 const char *cmd = RedisModule_StringPtrLen(bg->argv[cmd_pos], NULL);
137 RedisModuleCallReply *rep = RedisModule_Call(ctx, cmd, format, bg->argv + cmd_pos + 1, (size_t)bg->argc - cmd_pos - 1);
138 RedisModule_FreeString(NULL, format_redis_str);
139
140 /* Free the arguments within GIL to prevent simultaneous freeing in main thread. */
141 for (int i=0; i<bg->argc; i++)
142 RedisModule_FreeString(ctx, bg->argv[i]);
143 RedisModule_Free(bg->argv);
144 RedisModule_Free(bg);
145
146 // Release GIL
147 RedisModule_ThreadSafeContextUnlock(ctx);
148
149 // Reply to client
150 if (!rep) {
151 RedisModule_ReplyWithError(ctx, "NULL reply returned");
152 } else {
153 RedisModule_ReplyWithCallReply(ctx, rep);
154 RedisModule_FreeCallReply(rep);
155 }
156
157 // Unblock client
158 RedisModule_UnblockClient(bc, NULL);
159
160 // Free the Redis module context
161 RedisModule_FreeThreadSafeContext(ctx);
162
163 return NULL;
164}
165
166int do_bg_rm_call(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
167{
168 UNUSED(argv);
169 UNUSED(argc);
170
171 /* Make sure we're not trying to block a client when we shouldn't */
172 int flags = RedisModule_GetContextFlags(ctx);
173 int allFlags = RedisModule_GetContextFlagsAll();
174 if ((allFlags & REDISMODULE_CTX_FLAGS_MULTI) &&
175 (flags & REDISMODULE_CTX_FLAGS_MULTI)) {
176 RedisModule_ReplyWithSimpleString(ctx, "Blocked client is not supported inside multi");
177 return REDISMODULE_OK;
178 }
179 if ((allFlags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING) &&
180 (flags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING)) {
181 RedisModule_ReplyWithSimpleString(ctx, "Blocked client is not allowed");
182 return REDISMODULE_OK;
183 }
184
185 /* Make a copy of the arguments and pass them to the thread. */
186 bg_call_data *bg = RedisModule_Alloc(sizeof(bg_call_data));
187 bg->argv = RedisModule_Alloc(sizeof(RedisModuleString*)*argc);
188 bg->argc = argc;
189 for (int i=0; i<argc; i++)
190 bg->argv[i] = RedisModule_HoldString(ctx, argv[i]);
191
192 /* Block the client */
193 bg->bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
194
195 /* Start a thread to handle the request */
196 pthread_t tid;
197 int res = pthread_create(&tid, NULL, bg_call_worker, bg);
198 assert(res == 0);
199 pthread_detach(tid);
200
201 return REDISMODULE_OK;
202}
203
204int do_rm_call(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
205 UNUSED(argv);
206 UNUSED(argc);
207
208 if(argc < 2){
209 return RedisModule_WrongArity(ctx);
210 }
211
212 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
213
214 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "Ev", argv + 2, (size_t)argc - 2);
215 if(!rep){
216 RedisModule_ReplyWithError(ctx, "NULL reply returned");
217 }else{
218 RedisModule_ReplyWithCallReply(ctx, rep);
219 RedisModule_FreeCallReply(rep);
220 }
221
222 return REDISMODULE_OK;
223}
224
225static void rm_call_async_send_reply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) {
226 RedisModule_ReplyWithCallReply(ctx, reply);
227 RedisModule_FreeCallReply(reply);
228}
229
230/* Called when the command that was blocked on 'RM_Call' gets unblocked
231 * and send the reply to the blocked client. */
232static void rm_call_async_on_unblocked(RedisModuleCtx *ctx, RedisModuleCallReply *reply, void *private_data) {
233 UNUSED(ctx);
234 RedisModuleBlockedClient *bc = private_data;
235 RedisModuleCtx *bctx = RedisModule_GetThreadSafeContext(bc);
236 rm_call_async_send_reply(bctx, reply);
237 RedisModule_FreeThreadSafeContext(bctx);
238 RedisModule_UnblockClient(bc, RedisModule_BlockClientGetPrivateData(bc));
239}
240
241int do_rm_call_async_fire_and_forget(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
242 UNUSED(argv);
243 UNUSED(argc);
244
245 if(argc < 2){
246 return RedisModule_WrongArity(ctx);
247 }
248 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
249
250 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "!KEv", argv + 2, (size_t)argc - 2);
251
252 if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) {
253 RedisModule_ReplyWithCallReply(ctx, rep);
254 } else {
255 RedisModule_ReplyWithSimpleString(ctx, "Blocked");
256 }
257 RedisModule_FreeCallReply(rep);
258
259 return REDISMODULE_OK;
260}
261
262static void do_rm_call_async_free_pd(RedisModuleCtx * ctx, void *pd) {
263 UNUSED(ctx);
264 RedisModule_FreeCallReply(pd);
265}
266
267static void do_rm_call_async_disconnect(RedisModuleCtx *ctx, struct RedisModuleBlockedClient *bc) {
268 UNUSED(ctx);
269 RedisModuleCallReply* rep = RedisModule_BlockClientGetPrivateData(bc);
270 RedisModule_CallReplyPromiseAbort(rep, NULL);
271 RedisModule_FreeCallReply(rep);
272 RedisModule_AbortBlock(bc);
273}
274
275/*
276 * Callback for do_rm_call_async / do_rm_call_async_script_mode
277 * Gets the command to invoke as the first argument to the command and runs it,
278 * passing the rest of the arguments to the command invocation.
279 * If the command got blocked, blocks the client and unblock it when the command gets unblocked,
280 * this allows check the K (allow blocking) argument to RM_Call.
281 */
282int do_rm_call_async(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
283 UNUSED(argv);
284 UNUSED(argc);
285
286 if(argc < 2){
287 return RedisModule_WrongArity(ctx);
288 }
289
290 size_t format_len = 0;
291 char format[6] = {0};
292
293 if (!(RedisModule_GetContextFlags(ctx) & REDISMODULE_CTX_FLAGS_DENY_BLOCKING)) {
294 /* We are allowed to block the client so we can allow RM_Call to also block us */
295 format[format_len++] = 'K';
296 }
297
298 const char* invoked_cmd = RedisModule_StringPtrLen(argv[0], NULL);
299 if (strcasecmp(invoked_cmd, "do_rm_call_async_script_mode") == 0) {
300 format[format_len++] = 'S';
301 }
302
303 format[format_len++] = 'E';
304 format[format_len++] = 'v';
305 if (strcasecmp(invoked_cmd, "do_rm_call_async_no_replicate") != 0) {
306 /* Notice, without the '!' flag we will have inconsistency between master and replica.
307 * This is used only to check '!' flag correctness on blocked commands. */
308 format[format_len++] = '!';
309 }
310
311 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
312
313 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, format, argv + 2, (size_t)argc - 2);
314
315 if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) {
316 rm_call_async_send_reply(ctx, rep);
317 } else {
318 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, do_rm_call_async_free_pd, 0);
319 RedisModule_SetDisconnectCallback(bc, do_rm_call_async_disconnect);
320 RedisModule_BlockClientSetPrivateData(bc, rep);
321 RedisModule_CallReplyPromiseSetUnblockHandler(rep, rm_call_async_on_unblocked, bc);
322 }
323
324 return REDISMODULE_OK;
325}
326
327typedef struct ThreadedAsyncRMCallCtx{
328 RedisModuleBlockedClient *bc;
329 RedisModuleCallReply *reply;
330} ThreadedAsyncRMCallCtx;
331
332void *send_async_reply(void *arg) {
333 ThreadedAsyncRMCallCtx *ta_rm_call_ctx = arg;
334 rm_call_async_on_unblocked(NULL, ta_rm_call_ctx->reply, ta_rm_call_ctx->bc);
335 RedisModule_Free(ta_rm_call_ctx);
336 return NULL;
337}
338
339/* Called when the command that was blocked on 'RM_Call' gets unblocked
340 * and schedule a thread to send the reply to the blocked client. */
341static void rm_call_async_reply_on_thread(RedisModuleCtx *ctx, RedisModuleCallReply *reply, void *private_data) {
342 UNUSED(ctx);
343 ThreadedAsyncRMCallCtx *ta_rm_call_ctx = RedisModule_Alloc(sizeof(*ta_rm_call_ctx));
344 ta_rm_call_ctx->bc = private_data;
345 ta_rm_call_ctx->reply = reply;
346 pthread_t tid;
347 int res = pthread_create(&tid, NULL, send_async_reply, ta_rm_call_ctx);
348 assert(res == 0);
349 pthread_detach(tid);
350}
351
352/*
353 * Callback for do_rm_call_async_on_thread.
354 * Gets the command to invoke as the first argument to the command and runs it,
355 * passing the rest of the arguments to the command invocation.
356 * If the command got blocked, blocks the client and unblock on a background thread.
357 * this allows check the K (allow blocking) argument to RM_Call, and make sure that the reply
358 * that passes to unblock handler is owned by the handler and are not attached to any
359 * context that might be freed after the callback ends.
360 */
361int do_rm_call_async_on_thread(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
362 UNUSED(argv);
363 UNUSED(argc);
364
365 if(argc < 2){
366 return RedisModule_WrongArity(ctx);
367 }
368
369 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
370
371 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "KEv", argv + 2, (size_t)argc - 2);
372
373 if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) {
374 rm_call_async_send_reply(ctx, rep);
375 } else {
376 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
377 RedisModule_CallReplyPromiseSetUnblockHandler(rep, rm_call_async_reply_on_thread, bc);
378 RedisModule_FreeCallReply(rep);
379 }
380
381 return REDISMODULE_OK;
382}
383
384/* Private data for wait_and_do_rm_call_async that holds information about:
385 * 1. the block client, to unblock when done.
386 * 2. the arguments, contains the command to run using RM_Call */
387typedef struct WaitAndDoRMCallCtx {
388 RedisModuleBlockedClient *bc;
389 RedisModuleString **argv;
390 int argc;
391} WaitAndDoRMCallCtx;
392
393/*
394 * This callback will be called when the 'wait' command invoke on 'wait_and_do_rm_call_async' will finish.
395 * This callback will continue the execution flow just like 'do_rm_call_async' command.
396 */
397static void wait_and_do_rm_call_async_on_unblocked(RedisModuleCtx *ctx, RedisModuleCallReply *reply, void *private_data) {
398 WaitAndDoRMCallCtx *wctx = private_data;
399 if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_INTEGER) {
400 goto done;
401 }
402
403 if (RedisModule_CallReplyInteger(reply) != 1) {
404 goto done;
405 }
406
407 RedisModule_FreeCallReply(reply);
408 reply = NULL;
409
410 const char* cmd = RedisModule_StringPtrLen(wctx->argv[0], NULL);
411 reply = RedisModule_Call(ctx, cmd, "!EKv", wctx->argv + 1, (size_t)wctx->argc - 1);
412
413done:
414 if(RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_PROMISE) {
415 RedisModuleCtx *bctx = RedisModule_GetThreadSafeContext(wctx->bc);
416 rm_call_async_send_reply(bctx, reply);
417 RedisModule_FreeThreadSafeContext(bctx);
418 RedisModule_UnblockClient(wctx->bc, NULL);
419 } else {
420 RedisModule_CallReplyPromiseSetUnblockHandler(reply, rm_call_async_on_unblocked, wctx->bc);
421 RedisModule_FreeCallReply(reply);
422 }
423 for (int i = 0 ; i < wctx->argc ; ++i) {
424 RedisModule_FreeString(NULL, wctx->argv[i]);
425 }
426 RedisModule_Free(wctx->argv);
427 RedisModule_Free(wctx);
428}
429
430/*
431 * Callback for wait_and_do_rm_call
432 * Gets the command to invoke as the first argument, runs 'wait'
433 * command (using the K flag to RM_Call). Once the wait finished, runs the
434 * command that was given (just like 'do_rm_call_async').
435 */
436int wait_and_do_rm_call_async(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
437 UNUSED(argv);
438 UNUSED(argc);
439
440 if(argc < 2){
441 return RedisModule_WrongArity(ctx);
442 }
443
444 int flags = RedisModule_GetContextFlags(ctx);
445 if (flags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING) {
446 return RedisModule_ReplyWithError(ctx, "Err can not run wait, blocking is not allowed.");
447 }
448
449 RedisModuleCallReply* rep = RedisModule_Call(ctx, "wait", "!EKcc", "1", "0");
450 if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) {
451 rm_call_async_send_reply(ctx, rep);
452 } else {
453 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
454 WaitAndDoRMCallCtx *wctx = RedisModule_Alloc(sizeof(*wctx));
455 *wctx = (WaitAndDoRMCallCtx){
456 .bc = bc,
457 .argv = RedisModule_Alloc((argc - 1) * sizeof(RedisModuleString*)),
458 .argc = argc - 1,
459 };
460
461 for (int i = 1 ; i < argc ; ++i) {
462 wctx->argv[i - 1] = RedisModule_HoldString(NULL, argv[i]);
463 }
464 RedisModule_CallReplyPromiseSetUnblockHandler(rep, wait_and_do_rm_call_async_on_unblocked, wctx);
465 RedisModule_FreeCallReply(rep);
466 }
467
468 return REDISMODULE_OK;
469}
470
471static void blpop_and_set_multiple_keys_on_unblocked(RedisModuleCtx *ctx, RedisModuleCallReply *reply, void *private_data) {
472 /* ignore the reply */
473 RedisModule_FreeCallReply(reply);
474 WaitAndDoRMCallCtx *wctx = private_data;
475 for (int i = 0 ; i < wctx->argc ; i += 2) {
476 RedisModuleCallReply* rep = RedisModule_Call(ctx, "set", "!ss", wctx->argv[i], wctx->argv[i + 1]);
477 RedisModule_FreeCallReply(rep);
478 }
479
480 RedisModuleCtx *bctx = RedisModule_GetThreadSafeContext(wctx->bc);
481 RedisModule_ReplyWithSimpleString(bctx, "OK");
482 RedisModule_FreeThreadSafeContext(bctx);
483 RedisModule_UnblockClient(wctx->bc, NULL);
484
485 for (int i = 0 ; i < wctx->argc ; ++i) {
486 RedisModule_FreeString(NULL, wctx->argv[i]);
487 }
488 RedisModule_Free(wctx->argv);
489 RedisModule_Free(wctx);
490
491}
492
493/*
494 * Performs a blpop command on a given list and when unblocked set multiple string keys.
495 * This command allows checking that the unblock callback is performed as a unit
496 * and its effect are replicated to the replica and AOF wrapped with multi exec.
497 */
498int blpop_and_set_multiple_keys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
499 UNUSED(argv);
500 UNUSED(argc);
501
502 if(argc < 2 || argc % 2 != 0){
503 return RedisModule_WrongArity(ctx);
504 }
505
506 int flags = RedisModule_GetContextFlags(ctx);
507 if (flags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING) {
508 return RedisModule_ReplyWithError(ctx, "Err can not run wait, blocking is not allowed.");
509 }
510
511 RedisModuleCallReply* rep = RedisModule_Call(ctx, "blpop", "!EKsc", argv[1], "0");
512 if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) {
513 rm_call_async_send_reply(ctx, rep);
514 } else {
515 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
516 WaitAndDoRMCallCtx *wctx = RedisModule_Alloc(sizeof(*wctx));
517 *wctx = (WaitAndDoRMCallCtx){
518 .bc = bc,
519 .argv = RedisModule_Alloc((argc - 2) * sizeof(RedisModuleString*)),
520 .argc = argc - 2,
521 };
522
523 for (int i = 0 ; i < argc - 2 ; ++i) {
524 wctx->argv[i] = RedisModule_HoldString(NULL, argv[i + 2]);
525 }
526 RedisModule_CallReplyPromiseSetUnblockHandler(rep, blpop_and_set_multiple_keys_on_unblocked, wctx);
527 RedisModule_FreeCallReply(rep);
528 }
529
530 return REDISMODULE_OK;
531}
532
533/* simulate a blocked client replying to a thread safe context without creating a thread */
534int do_fake_bg_true(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
535 UNUSED(argv);
536 UNUSED(argc);
537
538 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
539 RedisModuleCtx *bctx = RedisModule_GetThreadSafeContext(bc);
540
541 RedisModule_ReplyWithBool(bctx, 1);
542
543 RedisModule_FreeThreadSafeContext(bctx);
544 RedisModule_UnblockClient(bc, NULL);
545
546 return REDISMODULE_OK;
547}
548
549
550/* this flag is used to work with busy commands, that might take a while
551 * and ability to stop the busy work with a different command*/
552static volatile int abort_flag = 0;
553
554int slow_fg_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
555 if (argc != 2) {
556 RedisModule_WrongArity(ctx);
557 return REDISMODULE_OK;
558 }
559 long long block_time = 0;
560 if (RedisModule_StringToLongLong(argv[1], &block_time) != REDISMODULE_OK) {
561 RedisModule_ReplyWithError(ctx, "Invalid integer value");
562 return REDISMODULE_OK;
563 }
564
565 uint64_t start_time = RedisModule_MonotonicMicroseconds();
566 /* when not blocking indefinitely, we don't process client commands in this test. */
567 int yield_flags = block_time? REDISMODULE_YIELD_FLAG_NONE: REDISMODULE_YIELD_FLAG_CLIENTS;
568 while (!abort_flag) {
569 RedisModule_Yield(ctx, yield_flags, "Slow module operation");
570 usleep(1000);
571 if (block_time && RedisModule_MonotonicMicroseconds() - start_time > (uint64_t)block_time)
572 break;
573 }
574
575 abort_flag = 0;
576 RedisModule_ReplyWithLongLong(ctx, 1);
577 return REDISMODULE_OK;
578}
579
580int stop_slow_fg_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
581 REDISMODULE_NOT_USED(argv);
582 REDISMODULE_NOT_USED(argc);
583 abort_flag = 1;
584 RedisModule_ReplyWithLongLong(ctx, 1);
585 return REDISMODULE_OK;
586}
587
588/* used to enable or disable slow operation in do_bg_rm_call */
589static int set_slow_bg_operation(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
590 if (argc != 2) {
591 RedisModule_WrongArity(ctx);
592 return REDISMODULE_OK;
593 }
594 long long ll;
595 if (RedisModule_StringToLongLong(argv[1], &ll) != REDISMODULE_OK) {
596 RedisModule_ReplyWithError(ctx, "Invalid integer value");
597 return REDISMODULE_OK;
598 }
599 g_slow_bg_operation = ll;
600 RedisModule_ReplyWithSimpleString(ctx, "OK");
601 return REDISMODULE_OK;
602}
603
604/* used to test if we reached the slow operation in do_bg_rm_call */
605static int is_in_slow_bg_operation(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
606 UNUSED(argv);
607 if (argc != 1) {
608 RedisModule_WrongArity(ctx);
609 return REDISMODULE_OK;
610 }
611
612 RedisModule_ReplyWithLongLong(ctx, g_is_in_slow_bg_operation);
613 return REDISMODULE_OK;
614}
615
616static void timer_callback(RedisModuleCtx *ctx, void *data)
617{
618 UNUSED(ctx);
619
620 RedisModuleBlockedClient *bc = data;
621
622 // Get Redis module context
623 RedisModuleCtx *reply_ctx = RedisModule_GetThreadSafeContext(bc);
624
625 // Reply to client
626 RedisModule_ReplyWithSimpleString(reply_ctx, "OK");
627
628 // Unblock client
629 RedisModule_UnblockClient(bc, NULL);
630
631 // Free the Redis module context
632 RedisModule_FreeThreadSafeContext(reply_ctx);
633}
634
635/* unblock_by_timer <period_ms> <timeout_ms>
636 * period_ms is the period of the timer.
637 * timeout_ms is the blocking timeout. */
638int unblock_by_timer(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
639{
640 if (argc != 3)
641 return RedisModule_WrongArity(ctx);
642
643 long long period;
644 long long timeout;
645 if (RedisModule_StringToLongLong(argv[1],&period) != REDISMODULE_OK)
646 return RedisModule_ReplyWithError(ctx,"ERR invalid period");
647 if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK) {
648 return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
649 }
650
651 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, timeout);
652 RedisModule_CreateTimer(ctx, period, timer_callback, bc);
653 return REDISMODULE_OK;
654}
655
656int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
657 REDISMODULE_NOT_USED(argv);
658 REDISMODULE_NOT_USED(argc);
659
660 if (RedisModule_Init(ctx, "blockedclient", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
661 return REDISMODULE_ERR;
662
663 if (RedisModule_CreateCommand(ctx, "acquire_gil", acquire_gil, "", 0, 0, 0) == REDISMODULE_ERR)
664 return REDISMODULE_ERR;
665
666 if (RedisModule_CreateCommand(ctx, "do_rm_call", do_rm_call,
667 "write", 0, 0, 0) == REDISMODULE_ERR)
668 return REDISMODULE_ERR;
669
670 if (RedisModule_CreateCommand(ctx, "do_rm_call_async", do_rm_call_async,
671 "write", 0, 0, 0) == REDISMODULE_ERR)
672 return REDISMODULE_ERR;
673
674 if (RedisModule_CreateCommand(ctx, "do_rm_call_async_on_thread", do_rm_call_async_on_thread,
675 "write", 0, 0, 0) == REDISMODULE_ERR)
676 return REDISMODULE_ERR;
677
678 if (RedisModule_CreateCommand(ctx, "do_rm_call_async_script_mode", do_rm_call_async,
679 "write", 0, 0, 0) == REDISMODULE_ERR)
680 return REDISMODULE_ERR;
681
682 if (RedisModule_CreateCommand(ctx, "do_rm_call_async_no_replicate", do_rm_call_async,
683 "write", 0, 0, 0) == REDISMODULE_ERR)
684 return REDISMODULE_ERR;
685
686 if (RedisModule_CreateCommand(ctx, "do_rm_call_fire_and_forget", do_rm_call_async_fire_and_forget,
687 "write", 0, 0, 0) == REDISMODULE_ERR)
688 return REDISMODULE_ERR;
689
690 if (RedisModule_CreateCommand(ctx, "wait_and_do_rm_call", wait_and_do_rm_call_async,
691 "write", 0, 0, 0) == REDISMODULE_ERR)
692 return REDISMODULE_ERR;
693
694 if (RedisModule_CreateCommand(ctx, "blpop_and_set_multiple_keys", blpop_and_set_multiple_keys,
695 "write", 0, 0, 0) == REDISMODULE_ERR)
696 return REDISMODULE_ERR;
697
698 if (RedisModule_CreateCommand(ctx, "do_bg_rm_call", do_bg_rm_call, "", 0, 0, 0) == REDISMODULE_ERR)
699 return REDISMODULE_ERR;
700
701 if (RedisModule_CreateCommand(ctx, "do_bg_rm_call_format", do_bg_rm_call, "", 0, 0, 0) == REDISMODULE_ERR)
702 return REDISMODULE_ERR;
703
704 if (RedisModule_CreateCommand(ctx, "do_fake_bg_true", do_fake_bg_true, "", 0, 0, 0) == REDISMODULE_ERR)
705 return REDISMODULE_ERR;
706
707 if (RedisModule_CreateCommand(ctx, "slow_fg_command", slow_fg_command,"", 0, 0, 0) == REDISMODULE_ERR)
708 return REDISMODULE_ERR;
709
710 if (RedisModule_CreateCommand(ctx, "stop_slow_fg_command", stop_slow_fg_command,"allow-busy", 0, 0, 0) == REDISMODULE_ERR)
711 return REDISMODULE_ERR;
712
713 if (RedisModule_CreateCommand(ctx, "set_slow_bg_operation", set_slow_bg_operation, "allow-busy", 0, 0, 0) == REDISMODULE_ERR)
714 return REDISMODULE_ERR;
715
716 if (RedisModule_CreateCommand(ctx, "is_in_slow_bg_operation", is_in_slow_bg_operation, "allow-busy", 0, 0, 0) == REDISMODULE_ERR)
717 return REDISMODULE_ERR;
718
719 if (RedisModule_CreateCommand(ctx, "unblock_by_timer", unblock_by_timer, "", 0, 0, 0) == REDISMODULE_ERR)
720 return REDISMODULE_ERR;
721
722 return REDISMODULE_OK;
723}
diff --git a/examples/redis-unstable/tests/modules/blockonbackground.c b/examples/redis-unstable/tests/modules/blockonbackground.c
new file mode 100644
index 0000000..7aeb011
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/blockonbackground.c
@@ -0,0 +1,333 @@
1#define _XOPEN_SOURCE 700
2#include "redismodule.h"
3#include <stdio.h>
4#include <stdlib.h>
5#include <pthread.h>
6#include <time.h>
7
8#define UNUSED(x) (void)(x)
9
10typedef struct {
11 /* Mutex for protecting RedisModule_BlockedClientMeasureTime*() API from race
12 * conditions due to timeout callback triggered in the main thread. */
13 pthread_mutex_t measuretime_mutex;
14 int measuretime_completed; /* Indicates that time measure has ended and will not continue further */
15 int myint; /* Used for replying */
16} BlockPrivdata;
17
18void blockClientPrivdataInit(RedisModuleBlockedClient *bc) {
19 BlockPrivdata *block_privdata = RedisModule_Calloc(1, sizeof(*block_privdata));
20 block_privdata->measuretime_mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
21 RedisModule_BlockClientSetPrivateData(bc, block_privdata);
22}
23
24void blockClientMeasureTimeStart(RedisModuleBlockedClient *bc, BlockPrivdata *block_privdata) {
25 pthread_mutex_lock(&block_privdata->measuretime_mutex);
26 RedisModule_BlockedClientMeasureTimeStart(bc);
27 pthread_mutex_unlock(&block_privdata->measuretime_mutex);
28}
29
30void blockClientMeasureTimeEnd(RedisModuleBlockedClient *bc, BlockPrivdata *block_privdata, int completed) {
31 pthread_mutex_lock(&block_privdata->measuretime_mutex);
32 if (!block_privdata->measuretime_completed) {
33 RedisModule_BlockedClientMeasureTimeEnd(bc);
34 if (completed) block_privdata->measuretime_completed = 1;
35 }
36 pthread_mutex_unlock(&block_privdata->measuretime_mutex);
37}
38
39/* Reply callback for blocking command BLOCK.DEBUG */
40int HelloBlock_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
41 UNUSED(argv);
42 UNUSED(argc);
43 BlockPrivdata *block_privdata = RedisModule_GetBlockedClientPrivateData(ctx);
44 return RedisModule_ReplyWithLongLong(ctx,block_privdata->myint);
45}
46
47/* Timeout callback for blocking command BLOCK.DEBUG */
48int HelloBlock_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
49 UNUSED(argv);
50 UNUSED(argc);
51 RedisModuleBlockedClient *bc = RedisModule_GetBlockedClientHandle(ctx);
52 BlockPrivdata *block_privdata = RedisModule_GetBlockedClientPrivateData(ctx);
53 blockClientMeasureTimeEnd(bc, block_privdata, 1);
54 return RedisModule_ReplyWithSimpleString(ctx,"Request timedout");
55}
56
57/* Private data freeing callback for BLOCK.DEBUG command. */
58void HelloBlock_FreeData(RedisModuleCtx *ctx, void *privdata) {
59 UNUSED(ctx);
60 BlockPrivdata *block_privdata = privdata;
61 pthread_mutex_destroy(&block_privdata->measuretime_mutex);
62 RedisModule_Free(privdata);
63}
64
65/* Private data freeing callback for BLOCK.BLOCK command. */
66void HelloBlock_FreeStringData(RedisModuleCtx *ctx, void *privdata) {
67 RedisModule_FreeString(ctx, (RedisModuleString*)privdata);
68}
69
70/* The thread entry point that actually executes the blocking part
71 * of the command BLOCK.DEBUG. */
72void *BlockDebug_ThreadMain(void *arg) {
73 void **targ = arg;
74 RedisModuleBlockedClient *bc = targ[0];
75 long long delay = (unsigned long)targ[1];
76 long long enable_time_track = (unsigned long)targ[2];
77 BlockPrivdata *block_privdata = RedisModule_BlockClientGetPrivateData(bc);
78
79 if (enable_time_track)
80 blockClientMeasureTimeStart(bc, block_privdata);
81 RedisModule_Free(targ);
82
83 struct timespec ts;
84 ts.tv_sec = delay / 1000;
85 ts.tv_nsec = (delay % 1000) * 1000000;
86 nanosleep(&ts, NULL);
87 if (enable_time_track)
88 blockClientMeasureTimeEnd(bc, block_privdata, 0);
89 block_privdata->myint = rand();
90 RedisModule_UnblockClient(bc,block_privdata);
91 return NULL;
92}
93
94/* The thread entry point that actually executes the blocking part
95 * of the command BLOCK.DOUBLE_DEBUG. */
96void *DoubleBlock_ThreadMain(void *arg) {
97 void **targ = arg;
98 RedisModuleBlockedClient *bc = targ[0];
99 long long delay = (unsigned long)targ[1];
100 BlockPrivdata *block_privdata = RedisModule_BlockClientGetPrivateData(bc);
101 blockClientMeasureTimeStart(bc, block_privdata);
102 RedisModule_Free(targ);
103 struct timespec ts;
104 ts.tv_sec = delay / 1000;
105 ts.tv_nsec = (delay % 1000) * 1000000;
106 nanosleep(&ts, NULL);
107 blockClientMeasureTimeEnd(bc, block_privdata, 0);
108 /* call again RedisModule_BlockedClientMeasureTimeStart() and
109 * RedisModule_BlockedClientMeasureTimeEnd and ensure that the
110 * total execution time is 2x the delay. */
111 blockClientMeasureTimeStart(bc, block_privdata);
112 nanosleep(&ts, NULL);
113 blockClientMeasureTimeEnd(bc, block_privdata, 0);
114 block_privdata->myint = rand();
115 RedisModule_UnblockClient(bc,block_privdata);
116 return NULL;
117}
118
119void HelloBlock_Disconnected(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc) {
120 RedisModule_Log(ctx,"warning","Blocked client %p disconnected!",
121 (void*)bc);
122}
123
124/* BLOCK.DEBUG <delay_ms> <timeout_ms> -- Block for <count> milliseconds, then reply with
125 * a random number. Timeout is the command timeout, so that you can test
126 * what happens when the delay is greater than the timeout. */
127int HelloBlock_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
128 if (argc != 3) return RedisModule_WrongArity(ctx);
129 long long delay;
130 long long timeout;
131
132 if (RedisModule_StringToLongLong(argv[1],&delay) != REDISMODULE_OK) {
133 return RedisModule_ReplyWithError(ctx,"ERR invalid count");
134 }
135
136 if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK) {
137 return RedisModule_ReplyWithError(ctx,"ERR invalid count");
138 }
139
140 pthread_t tid;
141 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout);
142 blockClientPrivdataInit(bc);
143
144 /* Here we set a disconnection handler, however since this module will
145 * block in sleep() in a thread, there is not much we can do in the
146 * callback, so this is just to show you the API. */
147 RedisModule_SetDisconnectCallback(bc,HelloBlock_Disconnected);
148
149 /* Now that we setup a blocking client, we need to pass the control
150 * to the thread. However we need to pass arguments to the thread:
151 * the delay and a reference to the blocked client handle. */
152 void **targ = RedisModule_Alloc(sizeof(void*)*3);
153 targ[0] = bc;
154 targ[1] = (void*)(unsigned long) delay;
155 // pass 1 as flag to enable time tracking
156 targ[2] = (void*)(unsigned long) 1;
157
158 if (pthread_create(&tid,NULL,BlockDebug_ThreadMain,targ) != 0) {
159 RedisModule_AbortBlock(bc);
160 return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
161 }
162 pthread_detach(tid);
163 return REDISMODULE_OK;
164}
165
166/* BLOCK.DEBUG_NOTRACKING <delay_ms> <timeout_ms> -- Block for <count> milliseconds, then reply with
167 * a random number. Timeout is the command timeout, so that you can test
168 * what happens when the delay is greater than the timeout.
169 * this command does not track background time so the background time should no appear in stats*/
170int HelloBlockNoTracking_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
171 if (argc != 3) return RedisModule_WrongArity(ctx);
172 long long delay;
173 long long timeout;
174
175 if (RedisModule_StringToLongLong(argv[1],&delay) != REDISMODULE_OK) {
176 return RedisModule_ReplyWithError(ctx,"ERR invalid count");
177 }
178
179 if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK) {
180 return RedisModule_ReplyWithError(ctx,"ERR invalid count");
181 }
182
183 pthread_t tid;
184 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout);
185 blockClientPrivdataInit(bc);
186
187 /* Here we set a disconnection handler, however since this module will
188 * block in sleep() in a thread, there is not much we can do in the
189 * callback, so this is just to show you the API. */
190 RedisModule_SetDisconnectCallback(bc,HelloBlock_Disconnected);
191
192 /* Now that we setup a blocking client, we need to pass the control
193 * to the thread. However we need to pass arguments to the thread:
194 * the delay and a reference to the blocked client handle. */
195 void **targ = RedisModule_Alloc(sizeof(void*)*3);
196 targ[0] = bc;
197 targ[1] = (void*)(unsigned long) delay;
198 // pass 0 as flag to enable time tracking
199 targ[2] = (void*)(unsigned long) 0;
200
201 if (pthread_create(&tid,NULL,BlockDebug_ThreadMain,targ) != 0) {
202 RedisModule_AbortBlock(bc);
203 return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
204 }
205 pthread_detach(tid);
206 return REDISMODULE_OK;
207}
208
209/* BLOCK.DOUBLE_DEBUG <delay_ms> -- Block for 2 x <count> milliseconds,
210 * then reply with a random number.
211 * This command is used to test multiple calls to RedisModule_BlockedClientMeasureTimeStart()
212 * and RedisModule_BlockedClientMeasureTimeEnd() within the same execution. */
213int HelloDoubleBlock_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
214 if (argc != 2) return RedisModule_WrongArity(ctx);
215 long long delay;
216
217 if (RedisModule_StringToLongLong(argv[1],&delay) != REDISMODULE_OK) {
218 return RedisModule_ReplyWithError(ctx,"ERR invalid count");
219 }
220
221 pthread_t tid;
222 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,0);
223 blockClientPrivdataInit(bc);
224
225 /* Now that we setup a blocking client, we need to pass the control
226 * to the thread. However we need to pass arguments to the thread:
227 * the delay and a reference to the blocked client handle. */
228 void **targ = RedisModule_Alloc(sizeof(void*)*2);
229 targ[0] = bc;
230 targ[1] = (void*)(unsigned long) delay;
231
232 if (pthread_create(&tid,NULL,DoubleBlock_ThreadMain,targ) != 0) {
233 RedisModule_AbortBlock(bc);
234 return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
235 }
236 pthread_detach(tid);
237 return REDISMODULE_OK;
238}
239
240RedisModuleBlockedClient *blocked_client = NULL;
241
242/* BLOCK.BLOCK [TIMEOUT] -- Blocks the current client until released
243 * or TIMEOUT seconds. If TIMEOUT is zero, no timeout function is
244 * registered.
245 */
246int Block_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
247 if (RedisModule_IsBlockedReplyRequest(ctx)) {
248 RedisModuleString *r = RedisModule_GetBlockedClientPrivateData(ctx);
249 return RedisModule_ReplyWithString(ctx, r);
250 } else if (RedisModule_IsBlockedTimeoutRequest(ctx)) {
251 RedisModule_UnblockClient(blocked_client, NULL); /* Must be called to avoid leaks. */
252 blocked_client = NULL;
253 return RedisModule_ReplyWithSimpleString(ctx, "Timed out");
254 }
255
256 if (argc != 2) return RedisModule_WrongArity(ctx);
257 long long timeout;
258
259 if (RedisModule_StringToLongLong(argv[1], &timeout) != REDISMODULE_OK) {
260 return RedisModule_ReplyWithError(ctx, "ERR invalid timeout");
261 }
262 if (blocked_client) {
263 return RedisModule_ReplyWithError(ctx, "ERR another client already blocked");
264 }
265
266 /* Block client. We use this function as both a reply and optional timeout
267 * callback and differentiate the different code flows above.
268 */
269 blocked_client = RedisModule_BlockClient(ctx, Block_RedisCommand,
270 timeout > 0 ? Block_RedisCommand : NULL, HelloBlock_FreeStringData, timeout);
271 return REDISMODULE_OK;
272}
273
274/* BLOCK.IS_BLOCKED -- Returns 1 if we have a blocked client, or 0 otherwise.
275 */
276int IsBlocked_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
277 UNUSED(argv);
278 UNUSED(argc);
279 RedisModule_ReplyWithLongLong(ctx, blocked_client ? 1 : 0);
280 return REDISMODULE_OK;
281}
282
283/* BLOCK.RELEASE [reply] -- Releases the blocked client and produce the specified reply.
284 */
285int Release_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
286 if (argc != 2) return RedisModule_WrongArity(ctx);
287 if (!blocked_client) {
288 return RedisModule_ReplyWithError(ctx, "ERR No blocked client");
289 }
290
291 RedisModuleString *replystr = argv[1];
292 RedisModule_RetainString(ctx, replystr);
293 RedisModule_UnblockClient(blocked_client, replystr);
294 blocked_client = NULL;
295
296 RedisModule_ReplyWithSimpleString(ctx, "OK");
297
298 return REDISMODULE_OK;
299}
300
301int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
302 UNUSED(argv);
303 UNUSED(argc);
304
305 if (RedisModule_Init(ctx,"block",1,REDISMODULE_APIVER_1)
306 == REDISMODULE_ERR) return REDISMODULE_ERR;
307
308 if (RedisModule_CreateCommand(ctx,"block.debug",
309 HelloBlock_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
310 return REDISMODULE_ERR;
311
312 if (RedisModule_CreateCommand(ctx,"block.double_debug",
313 HelloDoubleBlock_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
314 return REDISMODULE_ERR;
315
316 if (RedisModule_CreateCommand(ctx,"block.debug_no_track",
317 HelloBlockNoTracking_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
318 return REDISMODULE_ERR;
319
320 if (RedisModule_CreateCommand(ctx, "block.block",
321 Block_RedisCommand, "", 0, 0, 0) == REDISMODULE_ERR)
322 return REDISMODULE_ERR;
323
324 if (RedisModule_CreateCommand(ctx,"block.is_blocked",
325 IsBlocked_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
326 return REDISMODULE_ERR;
327
328 if (RedisModule_CreateCommand(ctx,"block.release",
329 Release_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
330 return REDISMODULE_ERR;
331
332 return REDISMODULE_OK;
333}
diff --git a/examples/redis-unstable/tests/modules/blockonkeys.c b/examples/redis-unstable/tests/modules/blockonkeys.c
new file mode 100644
index 0000000..94bb361
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/blockonkeys.c
@@ -0,0 +1,645 @@
1#include "redismodule.h"
2
3#include <string.h>
4#include <strings.h>
5#include <assert.h>
6#include <unistd.h>
7
8#define UNUSED(V) ((void) V)
9
10#define LIST_SIZE 1024
11
12/* The FSL (Fixed-Size List) data type is a low-budget imitation of the
13 * native Redis list, in order to test list-like commands implemented
14 * by a module.
15 * Examples: FSL.PUSH, FSL.BPOP, etc. */
16
17typedef struct {
18 long long list[LIST_SIZE];
19 long long length;
20} fsl_t; /* Fixed-size list */
21
22static RedisModuleType *fsltype = NULL;
23
24fsl_t *fsl_type_create(void) {
25 fsl_t *o;
26 o = RedisModule_Alloc(sizeof(*o));
27 o->length = 0;
28 return o;
29}
30
31void fsl_type_free(fsl_t *o) {
32 RedisModule_Free(o);
33}
34
35/* ========================== "fsltype" type methods ======================= */
36
37void *fsl_rdb_load(RedisModuleIO *rdb, int encver) {
38 if (encver != 0) {
39 return NULL;
40 }
41 fsl_t *fsl = fsl_type_create();
42 fsl->length = RedisModule_LoadUnsigned(rdb);
43 for (long long i = 0; i < fsl->length; i++)
44 fsl->list[i] = RedisModule_LoadSigned(rdb);
45 return fsl;
46}
47
48void fsl_rdb_save(RedisModuleIO *rdb, void *value) {
49 fsl_t *fsl = value;
50 RedisModule_SaveUnsigned(rdb,fsl->length);
51 for (long long i = 0; i < fsl->length; i++)
52 RedisModule_SaveSigned(rdb, fsl->list[i]);
53}
54
55void fsl_aofrw(RedisModuleIO *aof, RedisModuleString *key, void *value) {
56 fsl_t *fsl = value;
57 for (long long i = 0; i < fsl->length; i++)
58 RedisModule_EmitAOF(aof, "FSL.PUSH","sl", key, fsl->list[i]);
59}
60
61void fsl_free(void *value) {
62 fsl_type_free(value);
63}
64
65/* ========================== helper methods ======================= */
66
67/* Wrapper to the boilerplate code of opening a key, checking its type, etc.
68 * Returns 0 if `keyname` exists in the dataset, but it's of the wrong type (i.e. not FSL) */
69int get_fsl(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode, int create, fsl_t **fsl, int reply_on_failure) {
70 *fsl = NULL;
71 RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, mode);
72
73 if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_EMPTY) {
74 /* Key exists */
75 if (RedisModule_ModuleTypeGetType(key) != fsltype) {
76 /* Key is not FSL */
77 RedisModule_CloseKey(key);
78 if (reply_on_failure)
79 RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
80 RedisModuleCallReply *reply = RedisModule_Call(ctx, "INCR", "c", "fsl_wrong_type");
81 RedisModule_FreeCallReply(reply);
82 return 0;
83 }
84
85 *fsl = RedisModule_ModuleTypeGetValue(key);
86 if (*fsl && !(*fsl)->length && mode & REDISMODULE_WRITE) {
87 /* Key exists, but it's logically empty */
88 if (create) {
89 create = 0; /* No need to create, key exists in its basic state */
90 } else {
91 RedisModule_DeleteKey(key);
92 *fsl = NULL;
93 }
94 } else {
95 /* Key exists, and has elements in it - no need to create anything */
96 create = 0;
97 }
98 }
99
100 if (create) {
101 *fsl = fsl_type_create();
102 RedisModule_ModuleTypeSetValue(key, fsltype, *fsl);
103 }
104
105 RedisModule_CloseKey(key);
106 return 1;
107}
108
109/* ========================== commands ======================= */
110
111/* FSL.PUSH <key> <int> - Push an integer to the fixed-size list (to the right).
112 * It must be greater than the element in the head of the list. */
113int fsl_push(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
114 if (argc != 3)
115 return RedisModule_WrongArity(ctx);
116
117 long long ele;
118 if (RedisModule_StringToLongLong(argv[2],&ele) != REDISMODULE_OK)
119 return RedisModule_ReplyWithError(ctx,"ERR invalid integer");
120
121 fsl_t *fsl;
122 if (!get_fsl(ctx, argv[1], REDISMODULE_WRITE, 1, &fsl, 1))
123 return REDISMODULE_OK;
124
125 if (fsl->length == LIST_SIZE)
126 return RedisModule_ReplyWithError(ctx,"ERR list is full");
127
128 if (fsl->length != 0 && fsl->list[fsl->length-1] >= ele)
129 return RedisModule_ReplyWithError(ctx,"ERR new element has to be greater than the head element");
130
131 fsl->list[fsl->length++] = ele;
132 RedisModule_SignalKeyAsReady(ctx, argv[1]);
133
134 RedisModule_ReplicateVerbatim(ctx);
135
136 return RedisModule_ReplyWithSimpleString(ctx, "OK");
137}
138
139typedef struct {
140 RedisModuleString *keyname;
141 long long ele;
142} timer_data_t;
143
144static void timer_callback(RedisModuleCtx *ctx, void *data)
145{
146 timer_data_t *td = data;
147
148 fsl_t *fsl;
149 if (!get_fsl(ctx, td->keyname, REDISMODULE_WRITE, 1, &fsl, 1))
150 return;
151
152 if (fsl->length == LIST_SIZE)
153 return; /* list is full */
154
155 if (fsl->length != 0 && fsl->list[fsl->length-1] >= td->ele)
156 return; /* new element has to be greater than the head element */
157
158 fsl->list[fsl->length++] = td->ele;
159 RedisModule_SignalKeyAsReady(ctx, td->keyname);
160
161 RedisModule_Replicate(ctx, "FSL.PUSH", "sl", td->keyname, td->ele);
162
163 RedisModule_FreeString(ctx, td->keyname);
164 RedisModule_Free(td);
165}
166
167/* FSL.PUSHTIMER <key> <int> <period-in-ms> - Push the number 9000 to the fixed-size list (to the right).
168 * It must be greater than the element in the head of the list. */
169int fsl_pushtimer(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
170{
171 if (argc != 4)
172 return RedisModule_WrongArity(ctx);
173
174 long long ele;
175 if (RedisModule_StringToLongLong(argv[2],&ele) != REDISMODULE_OK)
176 return RedisModule_ReplyWithError(ctx,"ERR invalid integer");
177
178 long long period;
179 if (RedisModule_StringToLongLong(argv[3],&period) != REDISMODULE_OK)
180 return RedisModule_ReplyWithError(ctx,"ERR invalid period");
181
182 fsl_t *fsl;
183 if (!get_fsl(ctx, argv[1], REDISMODULE_WRITE, 1, &fsl, 1))
184 return REDISMODULE_OK;
185
186 if (fsl->length == LIST_SIZE)
187 return RedisModule_ReplyWithError(ctx,"ERR list is full");
188
189 timer_data_t *td = RedisModule_Alloc(sizeof(*td));
190 td->keyname = argv[1];
191 RedisModule_RetainString(ctx, td->keyname);
192 td->ele = ele;
193
194 RedisModuleTimerID id = RedisModule_CreateTimer(ctx, period, timer_callback, td);
195 RedisModule_ReplyWithLongLong(ctx, id);
196
197 return REDISMODULE_OK;
198}
199
200int bpop_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
201 REDISMODULE_NOT_USED(argv);
202 REDISMODULE_NOT_USED(argc);
203 RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
204
205 fsl_t *fsl;
206 if (!get_fsl(ctx, keyname, REDISMODULE_WRITE, 0, &fsl, 0) || !fsl)
207 return REDISMODULE_ERR;
208
209 RedisModule_Assert(fsl->length);
210 RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
211
212 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
213 RedisModule_ReplicateVerbatim(ctx);
214 return REDISMODULE_OK;
215}
216
217int bpop_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
218 REDISMODULE_NOT_USED(argv);
219 REDISMODULE_NOT_USED(argc);
220 return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
221}
222
223/* FSL.BPOP <key> <timeout> [NO_TO_CB]- Block clients until list has two or more elements.
224 * When that happens, unblock client and pop the last two elements (from the right). */
225int fsl_bpop(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
226 if (argc < 3)
227 return RedisModule_WrongArity(ctx);
228
229 long long timeout;
230 if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK || timeout < 0)
231 return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
232
233 int to_cb = 1;
234 if (argc == 4) {
235 if (strcasecmp("NO_TO_CB", RedisModule_StringPtrLen(argv[3], NULL)))
236 return RedisModule_ReplyWithError(ctx,"ERR invalid argument");
237 to_cb = 0;
238 }
239
240 fsl_t *fsl;
241 if (!get_fsl(ctx, argv[1], REDISMODULE_WRITE, 0, &fsl, 1))
242 return REDISMODULE_OK;
243
244 if (!fsl) {
245 RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, to_cb ? bpop_timeout_callback : NULL,
246 NULL, timeout, &argv[1], 1, NULL);
247 } else {
248 RedisModule_Assert(fsl->length);
249 RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
250 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
251 RedisModule_ReplicateVerbatim(ctx);
252 }
253
254 return REDISMODULE_OK;
255}
256
257int bpopgt_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
258 REDISMODULE_NOT_USED(argv);
259 REDISMODULE_NOT_USED(argc);
260 RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
261 long long *pgt = RedisModule_GetBlockedClientPrivateData(ctx);
262
263 fsl_t *fsl;
264 if (!get_fsl(ctx, keyname, REDISMODULE_WRITE, 0, &fsl, 0) || !fsl)
265 return RedisModule_ReplyWithError(ctx,"UNBLOCKED key no longer exists");
266
267 if (fsl->list[fsl->length-1] <= *pgt)
268 return REDISMODULE_ERR;
269
270 RedisModule_Assert(fsl->length);
271 RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
272 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
273 RedisModule_ReplicateVerbatim(ctx);
274 return REDISMODULE_OK;
275}
276
277int bpopgt_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
278 REDISMODULE_NOT_USED(argv);
279 REDISMODULE_NOT_USED(argc);
280 return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
281}
282
283void bpopgt_free_privdata(RedisModuleCtx *ctx, void *privdata) {
284 REDISMODULE_NOT_USED(ctx);
285 RedisModule_Free(privdata);
286}
287
288/* FSL.BPOPGT <key> <gt> <timeout> - Block clients until list has an element greater than <gt>.
289 * When that happens, unblock client and pop the last element (from the right). */
290int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
291 if (argc != 4)
292 return RedisModule_WrongArity(ctx);
293
294 long long gt;
295 if (RedisModule_StringToLongLong(argv[2],&gt) != REDISMODULE_OK)
296 return RedisModule_ReplyWithError(ctx,"ERR invalid integer");
297
298 long long timeout;
299 if (RedisModule_StringToLongLong(argv[3],&timeout) != REDISMODULE_OK || timeout < 0)
300 return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
301
302 fsl_t *fsl;
303 if (!get_fsl(ctx, argv[1], REDISMODULE_WRITE, 0, &fsl, 1))
304 return REDISMODULE_OK;
305
306 if (!fsl)
307 return RedisModule_ReplyWithError(ctx,"ERR key must exist");
308
309 if (fsl->list[fsl->length-1] <= gt) {
310 /* We use malloc so the tests in blockedonkeys.tcl can check for memory leaks */
311 long long *pgt = RedisModule_Alloc(sizeof(long long));
312 *pgt = gt;
313 RedisModule_BlockClientOnKeysWithFlags(
314 ctx, bpopgt_reply_callback, bpopgt_timeout_callback,
315 bpopgt_free_privdata, timeout, &argv[1], 1, pgt,
316 REDISMODULE_BLOCK_UNBLOCK_DELETED);
317 } else {
318 RedisModule_Assert(fsl->length);
319 RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
320 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
321 RedisModule_ReplicateVerbatim(ctx);
322 }
323
324 return REDISMODULE_OK;
325}
326
327int bpoppush_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
328 REDISMODULE_NOT_USED(argv);
329 REDISMODULE_NOT_USED(argc);
330 RedisModuleString *src_keyname = RedisModule_GetBlockedClientReadyKey(ctx);
331 RedisModuleString *dst_keyname = RedisModule_GetBlockedClientPrivateData(ctx);
332
333 fsl_t *src;
334 if (!get_fsl(ctx, src_keyname, REDISMODULE_WRITE, 0, &src, 0) || !src)
335 return REDISMODULE_ERR;
336
337 fsl_t *dst;
338 if (!get_fsl(ctx, dst_keyname, REDISMODULE_WRITE, 1, &dst, 0) || !dst)
339 return REDISMODULE_ERR;
340
341 RedisModule_Assert(src->length);
342 long long ele = src->list[--src->length];
343 dst->list[dst->length++] = ele;
344 RedisModule_SignalKeyAsReady(ctx, dst_keyname);
345 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
346 RedisModule_ReplicateVerbatim(ctx);
347 return RedisModule_ReplyWithLongLong(ctx, ele);
348}
349
350int bpoppush_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
351 REDISMODULE_NOT_USED(argv);
352 REDISMODULE_NOT_USED(argc);
353 return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
354}
355
356void bpoppush_free_privdata(RedisModuleCtx *ctx, void *privdata) {
357 RedisModule_FreeString(ctx, privdata);
358}
359
360/* FSL.BPOPPUSH <src> <dst> <timeout> - Block clients until <src> has an element.
361 * When that happens, unblock client, pop the last element from <src> and push it to <dst>
362 * (from the right). */
363int fsl_bpoppush(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
364 if (argc != 4)
365 return RedisModule_WrongArity(ctx);
366
367 long long timeout;
368 if (RedisModule_StringToLongLong(argv[3],&timeout) != REDISMODULE_OK || timeout < 0)
369 return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
370
371 fsl_t *src;
372 if (!get_fsl(ctx, argv[1], REDISMODULE_WRITE, 0, &src, 1))
373 return REDISMODULE_OK;
374
375 if (!src) {
376 /* Retain string for reply callback */
377 RedisModule_RetainString(ctx, argv[2]);
378 /* Key is empty, we must block */
379 RedisModule_BlockClientOnKeys(ctx, bpoppush_reply_callback, bpoppush_timeout_callback,
380 bpoppush_free_privdata, timeout, &argv[1], 1, argv[2]);
381 } else {
382 fsl_t *dst;
383 if (!get_fsl(ctx, argv[2], REDISMODULE_WRITE, 1, &dst, 1))
384 return REDISMODULE_OK;
385
386 RedisModule_Assert(src->length);
387 long long ele = src->list[--src->length];
388 dst->list[dst->length++] = ele;
389 RedisModule_SignalKeyAsReady(ctx, argv[2]);
390 RedisModule_ReplyWithLongLong(ctx, ele);
391 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
392 RedisModule_ReplicateVerbatim(ctx);
393 }
394
395 return REDISMODULE_OK;
396}
397
398/* FSL.GETALL <key> - Reply with an array containing all elements. */
399int fsl_getall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
400 if (argc != 2)
401 return RedisModule_WrongArity(ctx);
402
403 fsl_t *fsl;
404 if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
405 return REDISMODULE_OK;
406
407 if (!fsl)
408 return RedisModule_ReplyWithArray(ctx, 0);
409
410 RedisModule_ReplyWithArray(ctx, fsl->length);
411 for (int i = 0; i < fsl->length; i++)
412 RedisModule_ReplyWithLongLong(ctx, fsl->list[i]);
413 return REDISMODULE_OK;
414}
415
416/* Callback for blockonkeys_popall */
417int blockonkeys_popall_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
418 REDISMODULE_NOT_USED(argc);
419 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
420 if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_LIST) {
421 RedisModuleString *elem;
422 long len = 0;
423 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
424 while ((elem = RedisModule_ListPop(key, REDISMODULE_LIST_HEAD)) != NULL) {
425 len++;
426 RedisModule_ReplyWithString(ctx, elem);
427 RedisModule_FreeString(ctx, elem);
428 }
429 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
430 RedisModule_ReplicateVerbatim(ctx);
431 RedisModule_ReplySetArrayLength(ctx, len);
432 } else {
433 RedisModule_ReplyWithError(ctx, "ERR Not a list");
434 }
435 RedisModule_CloseKey(key);
436 return REDISMODULE_OK;
437}
438
439int blockonkeys_popall_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
440 REDISMODULE_NOT_USED(argv);
441 REDISMODULE_NOT_USED(argc);
442 return RedisModule_ReplyWithError(ctx, "ERR Timeout");
443}
444
445/* BLOCKONKEYS.POPALL key
446 *
447 * Blocks on an empty key for up to 3 seconds. When unblocked by a list
448 * operation like LPUSH, all the elements are popped and returned. Fails with an
449 * error on timeout. */
450int blockonkeys_popall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
451 if (argc != 2)
452 return RedisModule_WrongArity(ctx);
453
454 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
455 if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
456 RedisModule_BlockClientOnKeys(ctx, blockonkeys_popall_reply_callback,
457 blockonkeys_popall_timeout_callback,
458 NULL, 3000, &argv[1], 1, NULL);
459 } else {
460 RedisModule_ReplyWithError(ctx, "ERR Key not empty");
461 }
462 RedisModule_CloseKey(key);
463 return REDISMODULE_OK;
464}
465
466/* BLOCKONKEYS.LPUSH key val [val ..]
467 * BLOCKONKEYS.LPUSH_UNBLOCK key val [val ..]
468 *
469 * A module equivalent of LPUSH. If the name LPUSH_UNBLOCK is used,
470 * RM_SignalKeyAsReady() is also called. */
471int blockonkeys_lpush(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
472 if (argc < 3)
473 return RedisModule_WrongArity(ctx);
474
475 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
476 if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_EMPTY &&
477 RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST) {
478 RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
479 } else {
480 for (int i = 2; i < argc; i++) {
481 if (RedisModule_ListPush(key, REDISMODULE_LIST_HEAD,
482 argv[i]) != REDISMODULE_OK) {
483 RedisModule_CloseKey(key);
484 return RedisModule_ReplyWithError(ctx, "ERR Push failed");
485 }
486 }
487 }
488 RedisModule_CloseKey(key);
489
490 /* signal key as ready if the command is lpush_unblock */
491 size_t len;
492 const char *str = RedisModule_StringPtrLen(argv[0], &len);
493 if (!strncasecmp(str, "blockonkeys.lpush_unblock", len)) {
494 RedisModule_SignalKeyAsReady(ctx, argv[1]);
495 }
496 RedisModule_ReplicateVerbatim(ctx);
497 return RedisModule_ReplyWithSimpleString(ctx, "OK");
498}
499
500/* Callback for the BLOCKONKEYS.BLPOPN command */
501int blockonkeys_blpopn_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
502 REDISMODULE_NOT_USED(argc);
503 long long n;
504 RedisModule_StringToLongLong(argv[2], &n);
505 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
506 int result;
507 if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_LIST &&
508 RedisModule_ValueLength(key) >= (size_t)n) {
509 RedisModule_ReplyWithArray(ctx, n);
510 for (long i = 0; i < n; i++) {
511 RedisModuleString *elem = RedisModule_ListPop(key, REDISMODULE_LIST_HEAD);
512 RedisModule_ReplyWithString(ctx, elem);
513 RedisModule_FreeString(ctx, elem);
514 }
515 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
516 RedisModule_ReplicateVerbatim(ctx);
517 result = REDISMODULE_OK;
518 } else if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_LIST ||
519 RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
520 const char *module_cmd = RedisModule_StringPtrLen(argv[0], NULL);
521 if (!strcasecmp(module_cmd, "blockonkeys.blpopn_or_unblock"))
522 RedisModule_UnblockClient(RedisModule_GetBlockedClientHandle(ctx), NULL);
523
524 /* continue blocking */
525 result = REDISMODULE_ERR;
526 } else {
527 result = RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
528 }
529 RedisModule_CloseKey(key);
530 return result;
531}
532
533int blockonkeys_blpopn_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
534 REDISMODULE_NOT_USED(argv);
535 REDISMODULE_NOT_USED(argc);
536 return RedisModule_ReplyWithError(ctx, "ERR Timeout");
537}
538
539int blockonkeys_blpopn_abort_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
540 REDISMODULE_NOT_USED(argv);
541 REDISMODULE_NOT_USED(argc);
542 return RedisModule_ReplyWithSimpleString(ctx, "Action aborted");
543}
544
545/* BLOCKONKEYS.BLPOPN key N
546 *
547 * Blocks until key has N elements and then pops them or fails after 3 seconds.
548 */
549int blockonkeys_blpopn(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
550 if (argc < 3) return RedisModule_WrongArity(ctx);
551
552 long long n, timeout = 3000LL;
553 if (RedisModule_StringToLongLong(argv[2], &n) != REDISMODULE_OK) {
554 return RedisModule_ReplyWithError(ctx, "ERR Invalid N");
555 }
556
557 if (argc > 3 ) {
558 if (RedisModule_StringToLongLong(argv[3], &timeout) != REDISMODULE_OK) {
559 return RedisModule_ReplyWithError(ctx, "ERR Invalid timeout value");
560 }
561 }
562 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
563 int keytype = RedisModule_KeyType(key);
564 if (keytype != REDISMODULE_KEYTYPE_EMPTY &&
565 keytype != REDISMODULE_KEYTYPE_LIST) {
566 RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
567 } else if (keytype == REDISMODULE_KEYTYPE_LIST &&
568 RedisModule_ValueLength(key) >= (size_t)n) {
569 RedisModule_ReplyWithArray(ctx, n);
570 for (long i = 0; i < n; i++) {
571 RedisModuleString *elem = RedisModule_ListPop(key, REDISMODULE_LIST_HEAD);
572 RedisModule_ReplyWithString(ctx, elem);
573 RedisModule_FreeString(ctx, elem);
574 }
575 /* I'm lazy so i'll replicate a potentially blocking command, it shouldn't block in this flow. */
576 RedisModule_ReplicateVerbatim(ctx);
577 } else {
578 RedisModule_BlockClientOnKeys(ctx, blockonkeys_blpopn_reply_callback,
579 timeout ? blockonkeys_blpopn_timeout_callback : blockonkeys_blpopn_abort_callback,
580 NULL, timeout, &argv[1], 1, NULL);
581 }
582 RedisModule_CloseKey(key);
583 return REDISMODULE_OK;
584}
585
586int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
587 REDISMODULE_NOT_USED(argv);
588 REDISMODULE_NOT_USED(argc);
589
590 if (RedisModule_Init(ctx, "blockonkeys", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
591 return REDISMODULE_ERR;
592
593 RedisModuleTypeMethods tm = {
594 .version = REDISMODULE_TYPE_METHOD_VERSION,
595 .rdb_load = fsl_rdb_load,
596 .rdb_save = fsl_rdb_save,
597 .aof_rewrite = fsl_aofrw,
598 .mem_usage = NULL,
599 .free = fsl_free,
600 .digest = NULL,
601 };
602
603 fsltype = RedisModule_CreateDataType(ctx, "fsltype_t", 0, &tm);
604 if (fsltype == NULL)
605 return REDISMODULE_ERR;
606
607 if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"write",1,1,1) == REDISMODULE_ERR)
608 return REDISMODULE_ERR;
609
610 if (RedisModule_CreateCommand(ctx,"fsl.pushtimer",fsl_pushtimer,"write",1,1,1) == REDISMODULE_ERR)
611 return REDISMODULE_ERR;
612
613 if (RedisModule_CreateCommand(ctx,"fsl.bpop",fsl_bpop,"write",1,1,1) == REDISMODULE_ERR)
614 return REDISMODULE_ERR;
615
616 if (RedisModule_CreateCommand(ctx,"fsl.bpopgt",fsl_bpopgt,"write",1,1,1) == REDISMODULE_ERR)
617 return REDISMODULE_ERR;
618
619 if (RedisModule_CreateCommand(ctx,"fsl.bpoppush",fsl_bpoppush,"write",1,2,1) == REDISMODULE_ERR)
620 return REDISMODULE_ERR;
621
622 if (RedisModule_CreateCommand(ctx,"fsl.getall",fsl_getall,"",1,1,1) == REDISMODULE_ERR)
623 return REDISMODULE_ERR;
624
625 if (RedisModule_CreateCommand(ctx, "blockonkeys.popall", blockonkeys_popall,
626 "write", 1, 1, 1) == REDISMODULE_ERR)
627 return REDISMODULE_ERR;
628
629 if (RedisModule_CreateCommand(ctx, "blockonkeys.lpush", blockonkeys_lpush,
630 "write", 1, 1, 1) == REDISMODULE_ERR)
631 return REDISMODULE_ERR;
632
633 if (RedisModule_CreateCommand(ctx, "blockonkeys.lpush_unblock", blockonkeys_lpush,
634 "write", 1, 1, 1) == REDISMODULE_ERR)
635 return REDISMODULE_ERR;
636
637 if (RedisModule_CreateCommand(ctx, "blockonkeys.blpopn", blockonkeys_blpopn,
638 "write", 1, 1, 1) == REDISMODULE_ERR)
639 return REDISMODULE_ERR;
640
641 if (RedisModule_CreateCommand(ctx, "blockonkeys.blpopn_or_unblock", blockonkeys_blpopn,
642 "write", 1, 1, 1) == REDISMODULE_ERR)
643 return REDISMODULE_ERR;
644 return REDISMODULE_OK;
645}
diff --git a/examples/redis-unstable/tests/modules/cmdintrospection.c b/examples/redis-unstable/tests/modules/cmdintrospection.c
new file mode 100644
index 0000000..48208ff
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/cmdintrospection.c
@@ -0,0 +1,226 @@
1#include "redismodule.h"
2
3#define UNUSED(V) ((void) V)
4
5int cmd_xadd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
6 UNUSED(argv);
7 UNUSED(argc);
8 RedisModule_ReplyWithSimpleString(ctx, "OK");
9 return REDISMODULE_OK;
10}
11
12int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
13 REDISMODULE_NOT_USED(argv);
14 REDISMODULE_NOT_USED(argc);
15 if (RedisModule_Init(ctx, "cmdintrospection", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
16 return REDISMODULE_ERR;
17
18 if (RedisModule_CreateCommand(ctx,"cmdintrospection.xadd",cmd_xadd,"write deny-oom random fast",0,0,0) == REDISMODULE_ERR)
19 return REDISMODULE_ERR;
20
21 RedisModuleCommand *xadd = RedisModule_GetCommand(ctx,"cmdintrospection.xadd");
22
23 RedisModuleCommandInfo info = {
24 .version = REDISMODULE_COMMAND_INFO_VERSION,
25 .arity = -5,
26 .summary = "Appends a new message to a stream. Creates the key if it doesn't exist.",
27 .since = "5.0.0",
28 .complexity = "O(1) when adding a new entry, O(N) when trimming where N being the number of entries evicted.",
29 .tips = "nondeterministic_output",
30 .history = (RedisModuleCommandHistoryEntry[]){
31 /* NOTE: All versions specified should be the module's versions, not
32 * Redis'! We use Redis versions in this example for the purpose of
33 * testing (comparing the output with the output of the vanilla
34 * XADD). */
35 {"6.2.0", "Added the `NOMKSTREAM` option, `MINID` trimming strategy and the `LIMIT` option."},
36 {"7.0.0", "Added support for the `<ms>-*` explicit ID form."},
37 {"8.2.0", "Added the `KEEPREF`, `DELREF` and `ACKED` options."},
38 {0}
39 },
40 .key_specs = (RedisModuleCommandKeySpec[]){
41 {
42 .notes = "UPDATE instead of INSERT because of the optional trimming feature",
43 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
44 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
45 .bs.index.pos = 1,
46 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
47 .fk.range = {0,1,0}
48 },
49 {0}
50 },
51 .args = (RedisModuleCommandArg[]){
52 {
53 .name = "key",
54 .type = REDISMODULE_ARG_TYPE_KEY,
55 .key_spec_index = 0
56 },
57 {
58 .name = "nomkstream",
59 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
60 .token = "NOMKSTREAM",
61 .since = "6.2.0",
62 .flags = REDISMODULE_CMD_ARG_OPTIONAL
63 },
64 {
65 .name = "condition",
66 .type = REDISMODULE_ARG_TYPE_ONEOF,
67 .flags = REDISMODULE_CMD_ARG_OPTIONAL,
68 .subargs = (RedisModuleCommandArg[]){
69 {
70 .name = "keepref",
71 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
72 .token = "KEEPREF"
73 },
74 {
75 .name = "delref",
76 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
77 .token = "DELREF"
78 },
79 {
80 .name = "acked",
81 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
82 .token = "ACKED"
83 },
84 {0}
85 }
86 },
87 {
88 .name = "idmp",
89 .type = REDISMODULE_ARG_TYPE_ONEOF,
90 .flags = REDISMODULE_CMD_ARG_OPTIONAL,
91 .subargs = (RedisModuleCommandArg[]){
92 {
93 .name = "idmpauto-with-pid",
94 .type = REDISMODULE_ARG_TYPE_BLOCK,
95 .subargs = (RedisModuleCommandArg[]){
96 {
97 .name = "idmpauto-token",
98 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
99 .token = "IDMPAUTO"
100 },
101 {
102 .name = "pid",
103 .type = REDISMODULE_ARG_TYPE_STRING,
104 },
105 {0}
106 }
107 },
108 {
109 .name = "idmp-with-pid-iid",
110 .type = REDISMODULE_ARG_TYPE_BLOCK,
111 .subargs = (RedisModuleCommandArg[]){
112 {
113 .name = "idmp-token",
114 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
115 .token = "IDMP"
116 },
117 {
118 .name = "pid",
119 .type = REDISMODULE_ARG_TYPE_STRING,
120 },
121 {
122 .name = "iid",
123 .type = REDISMODULE_ARG_TYPE_STRING,
124 },
125 {0}
126 }
127 },
128 {0}
129 }
130 },
131 {
132 .name = "trim",
133 .type = REDISMODULE_ARG_TYPE_BLOCK,
134 .flags = REDISMODULE_CMD_ARG_OPTIONAL,
135 .subargs = (RedisModuleCommandArg[]){
136 {
137 .name = "strategy",
138 .type = REDISMODULE_ARG_TYPE_ONEOF,
139 .subargs = (RedisModuleCommandArg[]){
140 {
141 .name = "maxlen",
142 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
143 .token = "MAXLEN",
144 },
145 {
146 .name = "minid",
147 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
148 .token = "MINID",
149 .since = "6.2.0",
150 },
151 {0}
152 }
153 },
154 {
155 .name = "operator",
156 .type = REDISMODULE_ARG_TYPE_ONEOF,
157 .flags = REDISMODULE_CMD_ARG_OPTIONAL,
158 .subargs = (RedisModuleCommandArg[]){
159 {
160 .name = "equal",
161 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
162 .token = "="
163 },
164 {
165 .name = "approximately",
166 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
167 .token = "~"
168 },
169 {0}
170 }
171 },
172 {
173 .name = "threshold",
174 .type = REDISMODULE_ARG_TYPE_STRING,
175 .display_text = "threshold" /* Just for coverage, doesn't have a visible effect */
176 },
177 {
178 .name = "count",
179 .type = REDISMODULE_ARG_TYPE_INTEGER,
180 .token = "LIMIT",
181 .since = "6.2.0",
182 .flags = REDISMODULE_CMD_ARG_OPTIONAL
183 },
184 {0}
185 }
186 },
187 {
188 .name = "id-selector",
189 .type = REDISMODULE_ARG_TYPE_ONEOF,
190 .subargs = (RedisModuleCommandArg[]){
191 {
192 .name = "auto-id",
193 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
194 .token = "*"
195 },
196 {
197 .name = "id",
198 .type = REDISMODULE_ARG_TYPE_STRING,
199 },
200 {0}
201 }
202 },
203 {
204 .name = "data",
205 .type = REDISMODULE_ARG_TYPE_BLOCK,
206 .flags = REDISMODULE_CMD_ARG_MULTIPLE,
207 .subargs = (RedisModuleCommandArg[]){
208 {
209 .name = "field",
210 .type = REDISMODULE_ARG_TYPE_STRING,
211 },
212 {
213 .name = "value",
214 .type = REDISMODULE_ARG_TYPE_STRING,
215 },
216 {0}
217 }
218 },
219 {0}
220 }
221 };
222 if (RedisModule_SetCommandInfo(xadd, &info) == REDISMODULE_ERR)
223 return REDISMODULE_ERR;
224
225 return REDISMODULE_OK;
226}
diff --git a/examples/redis-unstable/tests/modules/commandfilter.c b/examples/redis-unstable/tests/modules/commandfilter.c
new file mode 100644
index 0000000..333b263
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/commandfilter.c
@@ -0,0 +1,251 @@
1#include "redismodule.h"
2
3#include <string.h>
4#include <strings.h>
5
6static RedisModuleString *log_key_name;
7
8static const char log_command_name[] = "commandfilter.log";
9static const char ping_command_name[] = "commandfilter.ping";
10static const char retained_command_name[] = "commandfilter.retained";
11static const char unregister_command_name[] = "commandfilter.unregister";
12static const char unfiltered_clientid_name[] = "unfilter_clientid";
13static int in_log_command = 0;
14
15unsigned long long unfiltered_clientid = 0;
16
17static RedisModuleCommandFilter *filter, *filter1;
18static RedisModuleString *retained;
19
20int CommandFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
21{
22 (void) argc;
23 (void) argv;
24
25 RedisModule_ReplyWithLongLong(ctx,
26 RedisModule_UnregisterCommandFilter(ctx, filter));
27
28 return REDISMODULE_OK;
29}
30
31int CommandFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
32{
33 (void) argc;
34 (void) argv;
35
36 RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "c", "@log");
37 if (reply) {
38 RedisModule_ReplyWithCallReply(ctx, reply);
39 RedisModule_FreeCallReply(reply);
40 } else {
41 RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
42 }
43
44 return REDISMODULE_OK;
45}
46
47int CommandFilter_Retained(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
48{
49 (void) argc;
50 (void) argv;
51
52 if (retained) {
53 RedisModule_ReplyWithString(ctx, retained);
54 } else {
55 RedisModule_ReplyWithNull(ctx);
56 }
57
58 return REDISMODULE_OK;
59}
60
61int CommandFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
62{
63 RedisModuleString *s = RedisModule_CreateString(ctx, "", 0);
64
65 int i;
66 for (i = 1; i < argc; i++) {
67 size_t arglen;
68 const char *arg = RedisModule_StringPtrLen(argv[i], &arglen);
69
70 if (i > 1) RedisModule_StringAppendBuffer(ctx, s, " ", 1);
71 RedisModule_StringAppendBuffer(ctx, s, arg, arglen);
72 }
73
74 RedisModuleKey *log = RedisModule_OpenKey(ctx, log_key_name, REDISMODULE_WRITE|REDISMODULE_READ);
75 RedisModule_ListPush(log, REDISMODULE_LIST_HEAD, s);
76 RedisModule_CloseKey(log);
77 RedisModule_FreeString(ctx, s);
78
79 in_log_command = 1;
80
81 size_t cmdlen;
82 const char *cmdname = RedisModule_StringPtrLen(argv[1], &cmdlen);
83 RedisModuleCallReply *reply = RedisModule_Call(ctx, cmdname, "v", &argv[2], (size_t)argc - 2);
84 if (reply) {
85 RedisModule_ReplyWithCallReply(ctx, reply);
86 RedisModule_FreeCallReply(reply);
87 } else {
88 RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
89 }
90
91 in_log_command = 0;
92
93 return REDISMODULE_OK;
94}
95
96int CommandFilter_UnfilteredClientId(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
97{
98 if (argc < 2)
99 return RedisModule_WrongArity(ctx);
100
101 long long id;
102 if (RedisModule_StringToLongLong(argv[1], &id) != REDISMODULE_OK) {
103 RedisModule_ReplyWithError(ctx, "invalid client id");
104 return REDISMODULE_OK;
105 }
106 if (id < 0) {
107 RedisModule_ReplyWithError(ctx, "invalid client id");
108 return REDISMODULE_OK;
109 }
110
111 unfiltered_clientid = id;
112 RedisModule_ReplyWithSimpleString(ctx, "OK");
113 return REDISMODULE_OK;
114}
115
116/* Filter to protect against Bug #11894 reappearing
117 *
118 * ensures that the filter is only run the first time through, and not on reprocessing
119 */
120void CommandFilter_BlmoveSwap(RedisModuleCommandFilterCtx *filter)
121{
122 if (RedisModule_CommandFilterArgsCount(filter) != 6)
123 return;
124
125 RedisModuleString *arg = RedisModule_CommandFilterArgGet(filter, 0);
126 size_t arg_len;
127 const char *arg_str = RedisModule_StringPtrLen(arg, &arg_len);
128
129 if (arg_len != 6 || strncmp(arg_str, "blmove", 6))
130 return;
131
132 /*
133 * Swapping directional args (right/left) from source and destination.
134 * need to hold here, can't push into the ArgReplace func, as it will cause other to freed -> use after free
135 */
136 RedisModuleString *dir1 = RedisModule_HoldString(NULL, RedisModule_CommandFilterArgGet(filter, 3));
137 RedisModuleString *dir2 = RedisModule_HoldString(NULL, RedisModule_CommandFilterArgGet(filter, 4));
138 RedisModule_CommandFilterArgReplace(filter, 3, dir2);
139 RedisModule_CommandFilterArgReplace(filter, 4, dir1);
140}
141
142void CommandFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
143{
144 unsigned long long id = RedisModule_CommandFilterGetClientId(filter);
145 if (id == unfiltered_clientid) return;
146
147 if (in_log_command) return; /* don't process our own RM_Call() from CommandFilter_LogCommand() */
148
149 /* Fun manipulations:
150 * - Remove @delme
151 * - Replace @replaceme
152 * - Append @insertbefore or @insertafter
153 * - Prefix with Log command if @log encountered
154 */
155 int log = 0;
156 int pos = 0;
157 while (pos < RedisModule_CommandFilterArgsCount(filter)) {
158 const RedisModuleString *arg = RedisModule_CommandFilterArgGet(filter, pos);
159 size_t arg_len;
160 const char *arg_str = RedisModule_StringPtrLen(arg, &arg_len);
161
162 if (arg_len == 6 && !memcmp(arg_str, "@delme", 6)) {
163 RedisModule_CommandFilterArgDelete(filter, pos);
164 continue;
165 }
166 if (arg_len == 10 && !memcmp(arg_str, "@replaceme", 10)) {
167 RedisModule_CommandFilterArgReplace(filter, pos,
168 RedisModule_CreateString(NULL, "--replaced--", 12));
169 } else if (arg_len == 13 && !memcmp(arg_str, "@insertbefore", 13)) {
170 RedisModule_CommandFilterArgInsert(filter, pos,
171 RedisModule_CreateString(NULL, "--inserted-before--", 19));
172 pos++;
173 } else if (arg_len == 12 && !memcmp(arg_str, "@insertafter", 12)) {
174 RedisModule_CommandFilterArgInsert(filter, pos + 1,
175 RedisModule_CreateString(NULL, "--inserted-after--", 18));
176 pos++;
177 } else if (arg_len == 7 && !memcmp(arg_str, "@retain", 7)) {
178 if (retained) RedisModule_FreeString(NULL, retained);
179 retained = RedisModule_CommandFilterArgGet(filter, pos + 1);
180 RedisModule_RetainString(NULL, retained);
181 pos++;
182 } else if (arg_len == 4 && !memcmp(arg_str, "@log", 4)) {
183 log = 1;
184 }
185 pos++;
186 }
187
188 if (log) RedisModule_CommandFilterArgInsert(filter, 0,
189 RedisModule_CreateString(NULL, log_command_name, sizeof(log_command_name)-1));
190}
191
192int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
193 if (RedisModule_Init(ctx,"commandfilter",1,REDISMODULE_APIVER_1)
194 == REDISMODULE_ERR) return REDISMODULE_ERR;
195
196 if (argc != 2 && argc != 3) {
197 RedisModule_Log(ctx, "warning", "Log key name not specified");
198 return REDISMODULE_ERR;
199 }
200
201 long long noself = 0;
202 log_key_name = RedisModule_CreateStringFromString(ctx, argv[0]);
203 RedisModule_StringToLongLong(argv[1], &noself);
204 retained = NULL;
205
206 if (RedisModule_CreateCommand(ctx,log_command_name,
207 CommandFilter_LogCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
208 return REDISMODULE_ERR;
209
210 if (RedisModule_CreateCommand(ctx,ping_command_name,
211 CommandFilter_PingCommand,"deny-oom",1,1,1) == REDISMODULE_ERR)
212 return REDISMODULE_ERR;
213
214 if (RedisModule_CreateCommand(ctx,retained_command_name,
215 CommandFilter_Retained,"readonly",1,1,1) == REDISMODULE_ERR)
216 return REDISMODULE_ERR;
217
218 if (RedisModule_CreateCommand(ctx,unregister_command_name,
219 CommandFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
220 return REDISMODULE_ERR;
221
222 if (RedisModule_CreateCommand(ctx, unfiltered_clientid_name,
223 CommandFilter_UnfilteredClientId, "admin", 1,1,1) == REDISMODULE_ERR)
224 return REDISMODULE_ERR;
225
226 if ((filter = RedisModule_RegisterCommandFilter(ctx, CommandFilter_CommandFilter,
227 noself ? REDISMODULE_CMDFILTER_NOSELF : 0))
228 == NULL) return REDISMODULE_ERR;
229
230 if ((filter1 = RedisModule_RegisterCommandFilter(ctx, CommandFilter_BlmoveSwap, 0)) == NULL)
231 return REDISMODULE_ERR;
232
233 if (argc == 3) {
234 const char *ptr = RedisModule_StringPtrLen(argv[2], NULL);
235 if (!strcasecmp(ptr, "noload")) {
236 /* This is a hint that we return ERR at the last moment of OnLoad. */
237 RedisModule_FreeString(ctx, log_key_name);
238 if (retained) RedisModule_FreeString(NULL, retained);
239 return REDISMODULE_ERR;
240 }
241 }
242
243 return REDISMODULE_OK;
244}
245
246int RedisModule_OnUnload(RedisModuleCtx *ctx) {
247 RedisModule_FreeString(ctx, log_key_name);
248 if (retained) RedisModule_FreeString(NULL, retained);
249
250 return REDISMODULE_OK;
251}
diff --git a/examples/redis-unstable/tests/modules/configaccess.c b/examples/redis-unstable/tests/modules/configaccess.c
new file mode 100644
index 0000000..7bc8a48
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/configaccess.c
@@ -0,0 +1,353 @@
1#include "redismodule.h"
2#include <assert.h>
3#include <string.h>
4
5/* See moduleconfigs.c for registering module configs. We need to register some
6 * module configs with our module in order to test the interaction between
7 * module configs and the RM_Get/Set*Config APIs. */
8int configaccess_bool;
9
10int getBoolConfigCommand(const char *name, void *privdata) {
11 REDISMODULE_NOT_USED(name);
12 return (*(int *)privdata);
13}
14
15int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
16 REDISMODULE_NOT_USED(name);
17 REDISMODULE_NOT_USED(err);
18 *(int *)privdata = new;
19 return REDISMODULE_OK;
20}
21
22/* Test command for RM_GetConfigType */
23int TestGetConfigType_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
24 if (argc != 2) {
25 return RedisModule_WrongArity(ctx);
26 }
27
28 size_t len;
29 const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
30
31 RedisModuleConfigType type;
32 int res = RedisModule_ConfigGetType(config_name, &type);
33 if (res == REDISMODULE_ERR) {
34 RedisModule_ReplyWithError(ctx, "ERR Config does not exist");
35 return REDISMODULE_ERR;
36 }
37
38 const char *type_str;
39 switch (type) {
40 case REDISMODULE_CONFIG_TYPE_BOOL:
41 type_str = "bool";
42 break;
43 case REDISMODULE_CONFIG_TYPE_NUMERIC:
44 type_str = "numeric";
45 break;
46 case REDISMODULE_CONFIG_TYPE_STRING:
47 type_str = "string";
48 break;
49 case REDISMODULE_CONFIG_TYPE_ENUM:
50 type_str = "enum";
51 break;
52 default:
53 assert(0);
54 break;
55 }
56
57 RedisModule_ReplyWithSimpleString(ctx, type_str);
58 return REDISMODULE_OK;
59}
60
61/* Test command for config iteration */
62int TestConfigIteration_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
63 REDISMODULE_NOT_USED(argv);
64
65 if (argc > 2) {
66 return RedisModule_WrongArity(ctx);
67 }
68
69 const char *pattern = NULL;
70 if (argc == 2) {
71 pattern = RedisModule_StringPtrLen(argv[1], NULL);
72 }
73
74 RedisModuleConfigIterator *iter = RedisModule_ConfigIteratorCreate(ctx, pattern);
75 if (!iter) {
76 RedisModule_ReplyWithError(ctx, "ERR Failed to get config iterator");
77 return REDISMODULE_ERR;
78 }
79
80 /* Start array reply for the configs */
81 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
82
83 /* Iterate through the dictionary */
84 const char *config_name = NULL;
85 long count = 0;
86 while ((config_name = RedisModule_ConfigIteratorNext(iter)) != NULL) {
87 RedisModuleString *value = NULL;
88 RedisModule_ConfigGet(ctx, config_name, &value);
89
90 RedisModule_ReplyWithArray(ctx, 2);
91 RedisModule_ReplyWithStringBuffer(ctx, config_name, strlen(config_name));
92 RedisModule_ReplyWithString(ctx, value);
93
94 RedisModule_FreeString(ctx, value);
95 ++count;
96 }
97 RedisModule_ReplySetArrayLength(ctx, count);
98
99 /* Free the iterator */
100 RedisModule_ConfigIteratorRelease(ctx, iter);
101
102 return REDISMODULE_OK;
103}
104
105/* Test command for RM_GetBoolConfig */
106int TestGetBoolConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
107 if (argc != 2) {
108 return RedisModule_WrongArity(ctx);
109 }
110
111 size_t len;
112 const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
113
114 int value;
115 if (RedisModule_ConfigGetBool(ctx, config_name, &value) == REDISMODULE_ERR) {
116 RedisModule_ReplyWithError(ctx, "ERR Failed to get bool config");
117 return REDISMODULE_ERR;
118 }
119
120 RedisModule_ReplyWithLongLong(ctx, value);
121 return REDISMODULE_OK;
122}
123
124/* Test command for RM_GetNumericConfig */
125int TestGetNumericConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
126 if (argc != 2) {
127 return RedisModule_WrongArity(ctx);
128 }
129
130 size_t len;
131 const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
132
133 long long value;
134 if (RedisModule_ConfigGetNumeric(ctx, config_name, &value) == REDISMODULE_ERR) {
135 RedisModule_ReplyWithError(ctx, "ERR Failed to get numeric config");
136 return REDISMODULE_ERR;
137 }
138
139 RedisModule_ReplyWithLongLong(ctx, value);
140 return REDISMODULE_OK;
141}
142
143/* Test command for RM_GetConfig */
144int TestGetConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
145 if (argc != 2) {
146 return RedisModule_WrongArity(ctx);
147 }
148
149 size_t len;
150 const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
151
152 RedisModuleString *value;
153 if (RedisModule_ConfigGet(ctx, config_name, &value) == REDISMODULE_ERR) {
154 RedisModule_ReplyWithError(ctx, "ERR Failed to get string config");
155 return REDISMODULE_ERR;
156 }
157
158 RedisModule_ReplyWithString(ctx, value);
159 RedisModule_FreeString(ctx,value);
160 return REDISMODULE_OK;
161}
162
163/* Test command for RM_GetEnumConfig */
164int TestGetEnumConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
165 if (argc != 2) {
166 return RedisModule_WrongArity(ctx);
167 }
168
169 size_t len;
170 const char *config_name = RedisModule_StringPtrLen(argv[1], &len);
171
172 RedisModuleString *value;
173 if (RedisModule_ConfigGetEnum(ctx, config_name, &value) == REDISMODULE_ERR) {
174 RedisModule_ReplyWithError(ctx, "ERR Failed to get enum name config");
175 return REDISMODULE_ERR;
176 }
177
178 RedisModule_ReplyWithString(ctx, value);
179 RedisModule_Free(value);
180 return REDISMODULE_OK;
181}
182
183/* Test command for RM_SetBoolConfig */
184int TestSetBoolConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
185 if (argc != 3) {
186 return RedisModule_WrongArity(ctx);
187 }
188
189 size_t name_len, value_len;
190 const char *config_name = RedisModule_StringPtrLen(argv[1], &name_len);
191 const char *config_value = RedisModule_StringPtrLen(argv[2], &value_len);
192
193 int bool_value;
194 if (!strcasecmp(config_value, "yes")) {
195 bool_value = 1;
196 } else if (!strcasecmp(config_value, "no")) {
197 bool_value = 0;
198 } else {
199 bool_value = -1;
200 }
201
202 RedisModuleString *error = NULL;
203 int result = RedisModule_ConfigSetBool(ctx, config_name, bool_value, &error);
204 if (result == REDISMODULE_ERR) {
205 RedisModule_ReplyWithErrorFormat(ctx, "ERR Failed to set bool config %s: %s", config_name, RedisModule_StringPtrLen(error, NULL));
206 RedisModule_FreeString(ctx, error);
207 return REDISMODULE_ERR;
208 }
209
210 RedisModule_ReplyWithSimpleString(ctx, "OK");
211 return REDISMODULE_OK;
212}
213
214/* Test command for RM_SetNumericConfig */
215int TestSetNumericConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
216 if (argc != 3) {
217 return RedisModule_WrongArity(ctx);
218 }
219
220 size_t name_len;
221 const char *config_name = RedisModule_StringPtrLen(argv[1], &name_len);
222
223 long long value;
224 const char *value_str= RedisModule_StringPtrLen(argv[2], NULL);
225 if (value_str[0] == '-') {
226 if (RedisModule_StringToLongLong(argv[2], &value) != REDISMODULE_OK) {
227 RedisModule_ReplyWithError(ctx, "ERR Invalid numeric value");
228 return REDISMODULE_ERR;
229 }
230 } else {
231 if (RedisModule_StringToULongLong(argv[2], (unsigned long long*)&value) != REDISMODULE_OK) {
232 RedisModule_ReplyWithError(ctx, "ERR Invalid numeric value");
233 return REDISMODULE_ERR;
234 }
235 }
236
237 RedisModuleString *error = NULL;
238 int result = RedisModule_ConfigSetNumeric(ctx, config_name, value, &error);
239 if (result == REDISMODULE_OK) {
240 RedisModule_ReplyWithSimpleString(ctx, "OK");
241 } else {
242 RedisModule_ReplyWithErrorFormat(ctx, "ERR Failed to set numeric config %s: %s", config_name, RedisModule_StringPtrLen(error, NULL));
243 RedisModule_FreeString(ctx, error);
244 return REDISMODULE_ERR;
245 }
246
247 return REDISMODULE_OK;
248}
249
250/* Test command for RM_SetConfig */
251int TestSetConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
252 if (argc != 3) {
253 return RedisModule_WrongArity(ctx);
254 }
255
256 size_t name_len;
257 const char *config_name = RedisModule_StringPtrLen(argv[1], &name_len);
258
259 RedisModuleString *error = NULL;
260 int result = RedisModule_ConfigSet(ctx, config_name, argv[2], &error);
261 if (result == REDISMODULE_OK) {
262 RedisModule_ReplyWithSimpleString(ctx, "OK");
263 } else {
264 RedisModule_ReplyWithErrorFormat(ctx, "ERR Failed to set string config %s: %s", config_name, RedisModule_StringPtrLen(error, NULL));
265 RedisModule_FreeString(ctx, error);
266 return REDISMODULE_ERR;
267 }
268
269 return REDISMODULE_OK;
270}
271
272/* Test command for RM_SetEnumConfig with name */
273int TestSetEnumConfig_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
274 if (argc < 3) {
275 return RedisModule_WrongArity(ctx);
276 }
277
278 const char *config_name = RedisModule_StringPtrLen(argv[1], NULL);
279
280 RedisModuleString *error = NULL;
281 int result = RedisModule_ConfigSetEnum(ctx, config_name, argv[2], &error);
282
283 if (result == REDISMODULE_OK) {
284 RedisModule_ReplyWithSimpleString(ctx, "OK");
285 } else {
286 RedisModule_ReplyWithErrorFormat(ctx, "ERR Failed to set enum config %s: %s", config_name, RedisModule_StringPtrLen(error, NULL));
287 RedisModule_FreeString(ctx, error);
288 return REDISMODULE_ERR;
289 }
290
291 return REDISMODULE_OK;
292}
293
294int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
295 REDISMODULE_NOT_USED(argv);
296 REDISMODULE_NOT_USED(argc);
297
298 if (RedisModule_Init(ctx, "configaccess", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
299 return REDISMODULE_ERR;
300
301 if (RedisModule_CreateCommand(ctx, "configaccess.getconfigs",
302 TestConfigIteration_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
303 return REDISMODULE_ERR;
304
305 if (RedisModule_CreateCommand(ctx, "configaccess.getbool",
306 TestGetBoolConfig_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
307 return REDISMODULE_ERR;
308
309 if (RedisModule_CreateCommand(ctx, "configaccess.getnumeric",
310 TestGetNumericConfig_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
311 return REDISMODULE_ERR;
312
313 if (RedisModule_CreateCommand(ctx, "configaccess.get",
314 TestGetConfig_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
315 return REDISMODULE_ERR;
316
317 if (RedisModule_CreateCommand(ctx, "configaccess.getenum",
318 TestGetEnumConfig_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
319 return REDISMODULE_ERR;
320
321 if (RedisModule_CreateCommand(ctx, "configaccess.setbool",
322 TestSetBoolConfig_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR)
323 return REDISMODULE_ERR;
324
325 if (RedisModule_CreateCommand(ctx, "configaccess.setnumeric",
326 TestSetNumericConfig_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR)
327 return REDISMODULE_ERR;
328
329 if (RedisModule_CreateCommand(ctx, "configaccess.set",
330 TestSetConfig_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR)
331 return REDISMODULE_ERR;
332
333 if (RedisModule_CreateCommand(ctx, "configaccess.setenum",
334 TestSetEnumConfig_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR)
335 return REDISMODULE_ERR;
336
337 if (RedisModule_CreateCommand(ctx, "configaccess.getconfigtype", TestGetConfigType_RedisCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR)
338 return REDISMODULE_ERR;
339
340 if (RedisModule_RegisterBoolConfig(ctx, "bool", 1, REDISMODULE_CONFIG_DEFAULT,
341 getBoolConfigCommand, setBoolConfigCommand, NULL, &configaccess_bool) == REDISMODULE_ERR) {
342 RedisModule_Log(ctx, "warning", "Failed to register configaccess_bool");
343 return REDISMODULE_ERR;
344 }
345
346 RedisModule_Log(ctx, "debug", "Loading configaccess module configuration");
347 if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
348 RedisModule_Log(ctx, "warning", "Failed to load configaccess module configuration");
349 return REDISMODULE_ERR;
350 }
351
352 return REDISMODULE_OK;
353}
diff --git a/examples/redis-unstable/tests/modules/crash.c b/examples/redis-unstable/tests/modules/crash.c
new file mode 100644
index 0000000..c5063d0
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/crash.c
@@ -0,0 +1,311 @@
1#include "redismodule.h"
2
3#include <strings.h>
4#include <sys/mman.h>
5
6#define UNUSED(V) ((void) V)
7
8void assertCrash(RedisModuleInfoCtx *ctx, int for_crash_report) {
9 UNUSED(ctx);
10 UNUSED(for_crash_report);
11 RedisModule_Assert(0);
12}
13
14void segfaultCrash(RedisModuleInfoCtx *ctx, int for_crash_report) {
15 UNUSED(ctx);
16 UNUSED(for_crash_report);
17 /* Compiler gives warnings about writing to a random address
18 * e.g "*((char*)-1) = 'x';". As a workaround, we map a read-only area
19 * and try to write there to trigger segmentation fault. */
20 char *p = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
21 *p = 'x';
22}
23
24int cmd_crash(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
25 UNUSED(ctx);
26 UNUSED(argv);
27 UNUSED(argc);
28
29 RedisModule_Assert(0);
30 return REDISMODULE_OK;
31}
32
33int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
34 REDISMODULE_NOT_USED(argv);
35 REDISMODULE_NOT_USED(argc);
36 if (RedisModule_Init(ctx,"modulecrash",1,REDISMODULE_APIVER_1)
37 == REDISMODULE_ERR) return REDISMODULE_ERR;
38
39 if (argc >= 1) {
40 if (!strcasecmp(RedisModule_StringPtrLen(argv[0], NULL), "segfault")) {
41 if (RedisModule_RegisterInfoFunc(ctx, segfaultCrash) == REDISMODULE_ERR) return REDISMODULE_ERR;
42 } else if (!strcasecmp(RedisModule_StringPtrLen(argv[0], NULL),"assert")) {
43 if (RedisModule_RegisterInfoFunc(ctx, assertCrash) == REDISMODULE_ERR) return REDISMODULE_ERR;
44 }
45 }
46
47 /* Create modulecrash.xadd command which is similar to xadd command.
48 * It will crash in the command handler to verify we print command tokens
49 * when hide-user-data-from-log config is enabled */
50 RedisModuleCommandInfo info = {
51 .version = REDISMODULE_COMMAND_INFO_VERSION,
52 .arity = -5,
53 .key_specs = (RedisModuleCommandKeySpec[]){
54 {
55 .notes = "UPDATE instead of INSERT because of the optional trimming feature",
56 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
57 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
58 .bs.index.pos = 1,
59 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
60 .fk.range = {0,1,0}
61 },
62 {0}
63 },
64 .args = (RedisModuleCommandArg[]){
65 {
66 .name = "key",
67 .type = REDISMODULE_ARG_TYPE_KEY,
68 .key_spec_index = 0
69 },
70 {
71 .name = "nomkstream",
72 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
73 .token = "NOMKSTREAM",
74 .since = "6.2.0",
75 .flags = REDISMODULE_CMD_ARG_OPTIONAL
76 },
77 {
78 .name = "trim",
79 .type = REDISMODULE_ARG_TYPE_BLOCK,
80 .flags = REDISMODULE_CMD_ARG_OPTIONAL,
81 .subargs = (RedisModuleCommandArg[]){
82 {
83 .name = "strategy",
84 .type = REDISMODULE_ARG_TYPE_ONEOF,
85 .subargs = (RedisModuleCommandArg[]){
86 {
87 .name = "maxlen",
88 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
89 .token = "MAXLEN",
90 },
91 {
92 .name = "minid",
93 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
94 .token = "MINID",
95 .since = "6.2.0",
96 },
97 {0}
98 }
99 },
100 {
101 .name = "operator",
102 .type = REDISMODULE_ARG_TYPE_ONEOF,
103 .flags = REDISMODULE_CMD_ARG_OPTIONAL,
104 .subargs = (RedisModuleCommandArg[]){
105 {
106 .name = "equal",
107 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
108 .token = "="
109 },
110 {
111 .name = "approximately",
112 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
113 .token = "~"
114 },
115 {0}
116 }
117 },
118 {
119 .name = "threshold",
120 .type = REDISMODULE_ARG_TYPE_STRING,
121 .display_text = "threshold" /* Just for coverage, doesn't have a visible effect */
122 },
123 {
124 .name = "count",
125 .type = REDISMODULE_ARG_TYPE_INTEGER,
126 .token = "LIMIT",
127 .since = "6.2.0",
128 .flags = REDISMODULE_CMD_ARG_OPTIONAL
129 },
130 {0}
131 }
132 },
133 {
134 .name = "id-selector",
135 .type = REDISMODULE_ARG_TYPE_ONEOF,
136 .subargs = (RedisModuleCommandArg[]){
137 {
138 .name = "auto-id",
139 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
140 .token = "*"
141 },
142 {
143 .name = "id",
144 .type = REDISMODULE_ARG_TYPE_STRING,
145 },
146 {0}
147 }
148 },
149 {
150 .name = "data",
151 .type = REDISMODULE_ARG_TYPE_BLOCK,
152 .flags = REDISMODULE_CMD_ARG_MULTIPLE,
153 .subargs = (RedisModuleCommandArg[]){
154 {
155 .name = "field",
156 .type = REDISMODULE_ARG_TYPE_STRING,
157 },
158 {
159 .name = "value",
160 .type = REDISMODULE_ARG_TYPE_STRING,
161 },
162 {0}
163 }
164 },
165 {0}
166 }
167 };
168
169 RedisModuleCommand *cmd;
170
171 if (RedisModule_CreateCommand(ctx,"modulecrash.xadd", cmd_crash,"write deny-oom random fast",0,0,0) == REDISMODULE_ERR)
172 return REDISMODULE_ERR;
173 cmd = RedisModule_GetCommand(ctx,"modulecrash.xadd");
174 if (RedisModule_SetCommandInfo(cmd, &info) == REDISMODULE_ERR)
175 return REDISMODULE_ERR;
176
177 /* Create a subcommand: modulecrash.parent sub
178 * It will crash in the command handler to verify we print subcommand name
179 * when hide-user-data-from-log config is enabled */
180 RedisModuleCommandInfo subcommand_info = {
181 .version = REDISMODULE_COMMAND_INFO_VERSION,
182 .arity = -5,
183 .key_specs = (RedisModuleCommandKeySpec[]){
184 {
185 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
186 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
187 .bs.index.pos = 1,
188 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
189 .fk.range = {0,1,0}
190 },
191 {0}
192 },
193 .args = (RedisModuleCommandArg[]){
194 {
195 .name = "key",
196 .type = REDISMODULE_ARG_TYPE_KEY,
197 .key_spec_index = 0
198 },
199 {
200 .name = "token",
201 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
202 .token = "TOKEN",
203 .flags = REDISMODULE_CMD_ARG_OPTIONAL
204 },
205 {
206 .name = "data",
207 .type = REDISMODULE_ARG_TYPE_BLOCK,
208 .subargs = (RedisModuleCommandArg[]){
209 {
210 .name = "field",
211 .type = REDISMODULE_ARG_TYPE_STRING,
212 },
213 {
214 .name = "value",
215 .type = REDISMODULE_ARG_TYPE_STRING,
216 },
217 {0}
218 }
219 },
220 {0}
221 }
222 };
223
224 if (RedisModule_CreateCommand(ctx,"modulecrash.parent",NULL,"",0,0,0) == REDISMODULE_ERR)
225 return REDISMODULE_ERR;
226
227 RedisModuleCommand *parent = RedisModule_GetCommand(ctx,"modulecrash.parent");
228
229 if (RedisModule_CreateSubcommand(parent,"subcmd",cmd_crash,"",0,0,0) == REDISMODULE_ERR)
230 return REDISMODULE_ERR;
231
232 cmd = RedisModule_GetCommand(ctx,"modulecrash.parent|subcmd");
233 if (RedisModule_SetCommandInfo(cmd, &subcommand_info) == REDISMODULE_ERR)
234 return REDISMODULE_ERR;
235
236 /* Create modulecrash.zunion command which is similar to zunion command.
237 * It will crash in the command handler to verify we print command tokens
238 * when hide-user-data-from-log config is enabled */
239 RedisModuleCommandInfo zunioninfo = {
240 .version = REDISMODULE_COMMAND_INFO_VERSION,
241 .arity = -5,
242 .key_specs = (RedisModuleCommandKeySpec[]){
243 {
244 .flags = REDISMODULE_CMD_KEY_RO,
245 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
246 .bs.index.pos = 1,
247 .find_keys_type = REDISMODULE_KSPEC_FK_KEYNUM,
248 .fk.keynum = {0,1,1}
249 },
250 {0}
251 },
252 .args = (RedisModuleCommandArg[]){
253 {
254 .name = "numkeys",
255 .type = REDISMODULE_ARG_TYPE_INTEGER,
256 },
257 {
258 .name = "key",
259 .type = REDISMODULE_ARG_TYPE_KEY,
260 .key_spec_index = 0,
261 .flags = REDISMODULE_CMD_ARG_MULTIPLE
262 },
263 {
264 .name = "weights",
265 .type = REDISMODULE_ARG_TYPE_INTEGER,
266 .token = "WEIGHTS",
267 .flags = REDISMODULE_CMD_ARG_OPTIONAL | REDISMODULE_CMD_ARG_MULTIPLE
268 },
269 {
270 .name = "aggregate",
271 .type = REDISMODULE_ARG_TYPE_ONEOF,
272 .token = "AGGREGATE",
273 .flags = REDISMODULE_CMD_ARG_OPTIONAL,
274 .subargs = (RedisModuleCommandArg[]){
275 {
276 .name = "sum",
277 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
278 .token = "sum"
279 },
280 {
281 .name = "min",
282 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
283 .token = "min"
284 },
285 {
286 .name = "max",
287 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
288 .token = "max"
289 },
290 {0}
291 }
292 },
293 {
294 .name = "withscores",
295 .type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
296 .token = "WITHSCORES",
297 .flags = REDISMODULE_CMD_ARG_OPTIONAL
298 },
299 {0}
300 }
301 };
302
303 if (RedisModule_CreateCommand(ctx,"modulecrash.zunion", cmd_crash,"readonly",0,0,0) == REDISMODULE_ERR)
304 return REDISMODULE_ERR;
305 cmd = RedisModule_GetCommand(ctx,"modulecrash.zunion");
306 if (RedisModule_SetCommandInfo(cmd, &zunioninfo) == REDISMODULE_ERR)
307 return REDISMODULE_ERR;
308
309
310 return REDISMODULE_OK;
311}
diff --git a/examples/redis-unstable/tests/modules/datatype.c b/examples/redis-unstable/tests/modules/datatype.c
new file mode 100644
index 0000000..05cf233
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/datatype.c
@@ -0,0 +1,323 @@
1/* This module current tests a small subset but should be extended in the future
2 * for general ModuleDataType coverage.
3 */
4
5/* define macros for having usleep */
6#define _BSD_SOURCE
7#define _DEFAULT_SOURCE
8#include <unistd.h>
9
10#include "redismodule.h"
11
12static RedisModuleType *datatype = NULL;
13static int load_encver = 0;
14
15/* used to test processing events during slow loading */
16static volatile int slow_loading = 0;
17static volatile int is_in_slow_loading = 0;
18
19#define DATATYPE_ENC_VER 1
20
21typedef struct {
22 long long intval;
23 RedisModuleString *strval;
24} DataType;
25
26static void *datatype_load(RedisModuleIO *io, int encver) {
27 load_encver = encver;
28 int intval = RedisModule_LoadSigned(io);
29 if (RedisModule_IsIOError(io)) return NULL;
30
31 RedisModuleString *strval = RedisModule_LoadString(io);
32 if (RedisModule_IsIOError(io)) return NULL;
33
34 DataType *dt = (DataType *) RedisModule_Alloc(sizeof(DataType));
35 dt->intval = intval;
36 dt->strval = strval;
37
38 if (slow_loading) {
39 RedisModuleCtx *ctx = RedisModule_GetContextFromIO(io);
40 is_in_slow_loading = 1;
41 while (slow_loading) {
42 RedisModule_Yield(ctx, REDISMODULE_YIELD_FLAG_CLIENTS, "Slow module operation");
43 usleep(1000);
44 }
45 is_in_slow_loading = 0;
46 }
47
48 return dt;
49}
50
51static void datatype_save(RedisModuleIO *io, void *value) {
52 DataType *dt = (DataType *) value;
53 RedisModule_SaveSigned(io, dt->intval);
54 RedisModule_SaveString(io, dt->strval);
55}
56
57static void datatype_free(void *value) {
58 if (value) {
59 DataType *dt = (DataType *) value;
60
61 if (dt->strval) RedisModule_FreeString(NULL, dt->strval);
62 RedisModule_Free(dt);
63 }
64}
65
66static void *datatype_copy(RedisModuleString *fromkey, RedisModuleString *tokey, const void *value) {
67 const DataType *old = value;
68
69 /* Answers to ultimate questions cannot be copied! */
70 if (old->intval == 42)
71 return NULL;
72
73 DataType *new = (DataType *) RedisModule_Alloc(sizeof(DataType));
74
75 new->intval = old->intval;
76 new->strval = RedisModule_CreateStringFromString(NULL, old->strval);
77
78 /* Breaking the rules here! We return a copy that also includes traces
79 * of fromkey/tokey to confirm we get what we expect.
80 */
81 size_t len;
82 const char *str = RedisModule_StringPtrLen(fromkey, &len);
83 RedisModule_StringAppendBuffer(NULL, new->strval, "/", 1);
84 RedisModule_StringAppendBuffer(NULL, new->strval, str, len);
85 RedisModule_StringAppendBuffer(NULL, new->strval, "/", 1);
86 str = RedisModule_StringPtrLen(tokey, &len);
87 RedisModule_StringAppendBuffer(NULL, new->strval, str, len);
88
89 return new;
90}
91
92static int datatype_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
93 if (argc != 4) {
94 RedisModule_WrongArity(ctx);
95 return REDISMODULE_OK;
96 }
97
98 long long intval;
99
100 if (RedisModule_StringToLongLong(argv[2], &intval) != REDISMODULE_OK) {
101 RedisModule_ReplyWithError(ctx, "Invalid integer value");
102 return REDISMODULE_OK;
103 }
104
105 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
106 DataType *dt = RedisModule_Calloc(sizeof(DataType), 1);
107 dt->intval = intval;
108 dt->strval = argv[3];
109 RedisModule_RetainString(ctx, dt->strval);
110
111 RedisModule_ModuleTypeSetValue(key, datatype, dt);
112 RedisModule_CloseKey(key);
113 RedisModule_ReplyWithSimpleString(ctx, "OK");
114
115 return REDISMODULE_OK;
116}
117
118static int datatype_restore(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
119 if (argc != 4) {
120 RedisModule_WrongArity(ctx);
121 return REDISMODULE_OK;
122 }
123
124 long long encver;
125 if (RedisModule_StringToLongLong(argv[3], &encver) != REDISMODULE_OK) {
126 RedisModule_ReplyWithError(ctx, "Invalid integer value");
127 return REDISMODULE_OK;
128 }
129
130 DataType *dt = RedisModule_LoadDataTypeFromStringEncver(argv[2], datatype, encver);
131 if (!dt) {
132 RedisModule_ReplyWithError(ctx, "Invalid data");
133 return REDISMODULE_OK;
134 }
135
136 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
137 RedisModule_ModuleTypeSetValue(key, datatype, dt);
138 RedisModule_CloseKey(key);
139 RedisModule_ReplyWithLongLong(ctx, load_encver);
140
141 return REDISMODULE_OK;
142}
143
144static int datatype_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
145 if (argc != 2) {
146 RedisModule_WrongArity(ctx);
147 return REDISMODULE_OK;
148 }
149
150 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
151 DataType *dt = RedisModule_ModuleTypeGetValue(key);
152 RedisModule_CloseKey(key);
153
154 if (!dt) {
155 RedisModule_ReplyWithNullArray(ctx);
156 } else {
157 RedisModule_ReplyWithArray(ctx, 2);
158 RedisModule_ReplyWithLongLong(ctx, dt->intval);
159 RedisModule_ReplyWithString(ctx, dt->strval);
160 }
161 return REDISMODULE_OK;
162}
163
164static int datatype_dump(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
165 if (argc != 2) {
166 RedisModule_WrongArity(ctx);
167 return REDISMODULE_OK;
168 }
169
170 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
171 DataType *dt = RedisModule_ModuleTypeGetValue(key);
172 RedisModule_CloseKey(key);
173
174 RedisModuleString *reply = RedisModule_SaveDataTypeToString(ctx, dt, datatype);
175 if (!reply) {
176 RedisModule_ReplyWithError(ctx, "Failed to save");
177 return REDISMODULE_OK;
178 }
179
180 RedisModule_ReplyWithString(ctx, reply);
181 RedisModule_FreeString(ctx, reply);
182 return REDISMODULE_OK;
183}
184
185static int datatype_swap(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
186 if (argc != 3) {
187 RedisModule_WrongArity(ctx);
188 return REDISMODULE_OK;
189 }
190
191 RedisModuleKey *a = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
192 RedisModuleKey *b = RedisModule_OpenKey(ctx, argv[2], REDISMODULE_WRITE);
193 void *val = RedisModule_ModuleTypeGetValue(a);
194
195 int error = (RedisModule_ModuleTypeReplaceValue(b, datatype, val, &val) == REDISMODULE_ERR ||
196 RedisModule_ModuleTypeReplaceValue(a, datatype, val, NULL) == REDISMODULE_ERR);
197 if (!error)
198 RedisModule_ReplyWithSimpleString(ctx, "OK");
199 else
200 RedisModule_ReplyWithError(ctx, "ERR failed");
201
202 RedisModule_CloseKey(a);
203 RedisModule_CloseKey(b);
204
205 return REDISMODULE_OK;
206}
207
208/* used to enable or disable slow loading */
209static int datatype_slow_loading(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
210 if (argc != 2) {
211 RedisModule_WrongArity(ctx);
212 return REDISMODULE_OK;
213 }
214
215 long long ll;
216 if (RedisModule_StringToLongLong(argv[1], &ll) != REDISMODULE_OK) {
217 RedisModule_ReplyWithError(ctx, "Invalid integer value");
218 return REDISMODULE_OK;
219 }
220 slow_loading = ll;
221 RedisModule_ReplyWithSimpleString(ctx, "OK");
222 return REDISMODULE_OK;
223}
224
225/* used to test if we reached the slow loading code */
226static int datatype_is_in_slow_loading(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
227 REDISMODULE_NOT_USED(argv);
228 if (argc != 1) {
229 RedisModule_WrongArity(ctx);
230 return REDISMODULE_OK;
231 }
232
233 RedisModule_ReplyWithLongLong(ctx, is_in_slow_loading);
234 return REDISMODULE_OK;
235}
236
237int createDataTypeBlockCheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
238 REDISMODULE_NOT_USED(argv);
239 REDISMODULE_NOT_USED(argc);
240 static RedisModuleType *datatype_outside_onload = NULL;
241
242 RedisModuleTypeMethods datatype_methods = {
243 .version = REDISMODULE_TYPE_METHOD_VERSION,
244 .rdb_load = datatype_load,
245 .rdb_save = datatype_save,
246 .free = datatype_free,
247 .copy = datatype_copy
248 };
249
250 datatype_outside_onload = RedisModule_CreateDataType(ctx, "test_dt_outside_onload", 1, &datatype_methods);
251
252 /* This validates that it's not possible to create datatype outside OnLoad,
253 * thus returns an error if it succeeds. */
254 if (datatype_outside_onload == NULL) {
255 RedisModule_ReplyWithSimpleString(ctx, "OK");
256 } else {
257 RedisModule_ReplyWithError(ctx, "UNEXPECTEDOK");
258 }
259 return REDISMODULE_OK;
260}
261
262int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
263 REDISMODULE_NOT_USED(argv);
264 REDISMODULE_NOT_USED(argc);
265
266 if (RedisModule_Init(ctx,"datatype",DATATYPE_ENC_VER,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
267 return REDISMODULE_ERR;
268
269 /* Creates a command which creates a datatype outside OnLoad() function. */
270 if (RedisModule_CreateCommand(ctx,"block.create.datatype.outside.onload", createDataTypeBlockCheck, "write", 0, 0, 0) == REDISMODULE_ERR)
271 return REDISMODULE_ERR;
272
273 RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS);
274
275 RedisModuleTypeMethods datatype_methods = {
276 .version = REDISMODULE_TYPE_METHOD_VERSION,
277 .rdb_load = datatype_load,
278 .rdb_save = datatype_save,
279 .free = datatype_free,
280 .copy = datatype_copy
281 };
282
283 datatype = RedisModule_CreateDataType(ctx, "test___dt", 1, &datatype_methods);
284 if (datatype == NULL)
285 return REDISMODULE_ERR;
286
287 if (RedisModule_CreateCommand(ctx,"datatype.set", datatype_set,
288 "write deny-oom", 1, 1, 1) == REDISMODULE_ERR)
289 return REDISMODULE_ERR;
290
291 if (RedisModule_CreateCommand(ctx,"datatype.get", datatype_get,"",1,1,1) == REDISMODULE_ERR)
292 return REDISMODULE_ERR;
293
294 if (RedisModule_CreateCommand(ctx,"datatype.restore", datatype_restore,
295 "write deny-oom", 1, 1, 1) == REDISMODULE_ERR)
296 return REDISMODULE_ERR;
297
298 if (RedisModule_CreateCommand(ctx,"datatype.dump", datatype_dump,"",1,1,1) == REDISMODULE_ERR)
299 return REDISMODULE_ERR;
300
301 if (RedisModule_CreateCommand(ctx, "datatype.swap", datatype_swap,
302 "write", 1, 1, 1) == REDISMODULE_ERR)
303 return REDISMODULE_ERR;
304
305 if (RedisModule_CreateCommand(ctx, "datatype.slow_loading", datatype_slow_loading,
306 "allow-loading", 0, 0, 0) == REDISMODULE_ERR)
307 return REDISMODULE_ERR;
308
309 if (RedisModule_CreateCommand(ctx, "datatype.is_in_slow_loading", datatype_is_in_slow_loading,
310 "allow-loading", 0, 0, 0) == REDISMODULE_ERR)
311 return REDISMODULE_ERR;
312
313 return REDISMODULE_OK;
314}
315
316int RedisModule_OnUnload(RedisModuleCtx *ctx) {
317 REDISMODULE_NOT_USED(ctx);
318 if (datatype) {
319 RedisModule_Free(datatype);
320 datatype = NULL;
321 }
322 return REDISMODULE_OK;
323}
diff --git a/examples/redis-unstable/tests/modules/datatype2.c b/examples/redis-unstable/tests/modules/datatype2.c
new file mode 100644
index 0000000..bc0dc3d
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/datatype2.c
@@ -0,0 +1,739 @@
1/* This module is used to test a use case of a module that stores information
2 * about keys in global memory, and relies on the enhanced data type callbacks to
3 * get key name and dbid on various operations.
4 *
5 * it simulates a simple memory allocator. The smallest allocation unit of
6 * the allocator is a mem block with a size of 4KB. Multiple mem blocks are combined
7 * using a linked list. These linked lists are placed in a global dict named 'mem_pool'.
8 * Each db has a 'mem_pool'. You can use the 'mem.alloc' command to allocate a specified
9 * number of mem blocks, and use 'mem.free' to release the memory. Use 'mem.write', 'mem.read'
10 * to write and read the specified mem block (note that each mem block can only be written once).
11 * Use 'mem.usage' to get the memory usage under different dbs, and it will return the size
12 * mem blocks and used mem blocks under the db.
13 * The specific structure diagram is as follows:
14 *
15 *
16 * Global variables of the module:
17 *
18 * mem blocks link
19 * ┌─────┬─────┐
20 * │ │ │ ┌───┐ ┌───┐ ┌───┐
21 * │ k1 │ ───┼───►│4KB├───►│4KB├───►│4KB│
22 * │ │ │ └───┘ └───┘ └───┘
23 * ├─────┼─────┤
24 * ┌───────┐ ┌────► │ │ │ ┌───┐ ┌───┐
25 * │ │ │ │ k2 │ ───┼───►│4KB├───►│4KB│
26 * │ db0 ├──────┘ │ │ │ └───┘ └───┘
27 * │ │ ├─────┼─────┤
28 * ├───────┤ │ │ │ ┌───┐ ┌───┐ ┌───┐
29 * │ │ │ k3 │ ───┼───►│4KB├───►│4KB├───►│4KB│
30 * │ db1 ├──►null │ │ │ └───┘ └───┘ └───┘
31 * │ │ └─────┴─────┘
32 * ├───────┤ dict
33 * │ │
34 * │ db2 ├─────────┐
35 * │ │ │
36 * ├───────┤ │ ┌─────┬─────┐
37 * │ │ │ │ │ │ ┌───┐ ┌───┐ ┌───┐
38 * │ db3 ├──►null │ │ k1 │ ───┼───►│4KB├───►│4KB├───►│4KB│
39 * │ │ │ │ │ │ └───┘ └───┘ └───┘
40 * └───────┘ │ ├─────┼─────┤
41 * mem_pool[MAX_DB] │ │ │ │ ┌───┐ ┌───┐
42 * └──►│ k2 │ ───┼───►│4KB├───►│4KB│
43 * │ │ │ └───┘ └───┘
44 * └─────┴─────┘
45 * dict
46 *
47 *
48 * Keys in redis database:
49 *
50 * ┌───────┐
51 * │ size │
52 * ┌───────────►│ used │
53 * │ │ mask │
54 * ┌─────┬─────┐ │ └───────┘ ┌───────┐
55 * │ │ │ │ MemAllocObject │ size │
56 * │ k1 │ ───┼─┘ ┌───────────►│ used │
57 * │ │ │ │ │ mask │
58 * ├─────┼─────┤ ┌───────┐ ┌─────┬─────┐ │ └───────┘
59 * │ │ │ │ size │ │ │ │ │ MemAllocObject
60 * │ k2 │ ───┼─────────────►│ used │ │ k1 │ ───┼─┘
61 * │ │ │ │ mask │ │ │ │
62 * ├─────┼─────┤ └───────┘ ├─────┼─────┤
63 * │ │ │ MemAllocObject │ │ │
64 * │ k3 │ ───┼─┐ │ k2 │ ───┼─┐
65 * │ │ │ │ │ │ │ │
66 * └─────┴─────┘ │ ┌───────┐ └─────┴─────┘ │ ┌───────┐
67 * redis db[0] │ │ size │ redis db[1] │ │ size │
68 * └───────────►│ used │ └───────────►│ used │
69 * │ mask │ │ mask │
70 * └───────┘ └───────┘
71 * MemAllocObject MemAllocObject
72 *
73 **/
74
75#include "redismodule.h"
76#include <stdio.h>
77#include <stdlib.h>
78#include <ctype.h>
79#include <string.h>
80#include <stdint.h>
81
82static RedisModuleType *MemAllocType;
83
84#define MAX_DB 16
85RedisModuleDict *mem_pool[MAX_DB];
86typedef struct MemAllocObject {
87 long long size;
88 long long used;
89 uint64_t mask;
90} MemAllocObject;
91
92MemAllocObject *createMemAllocObject(void) {
93 MemAllocObject *o = RedisModule_Calloc(1, sizeof(*o));
94 return o;
95}
96
97/*---------------------------- mem block apis ------------------------------------*/
98#define BLOCK_SIZE 4096
99struct MemBlock {
100 char block[BLOCK_SIZE];
101 struct MemBlock *next;
102};
103
104void MemBlockFree(struct MemBlock *head) {
105 if (head) {
106 struct MemBlock *block = head->next, *next;
107 RedisModule_Free(head);
108 while (block) {
109 next = block->next;
110 RedisModule_Free(block);
111 block = next;
112 }
113 }
114}
115struct MemBlock *MemBlockCreate(long long num) {
116 if (num <= 0) {
117 return NULL;
118 }
119
120 struct MemBlock *head = RedisModule_Calloc(1, sizeof(struct MemBlock));
121 struct MemBlock *block = head;
122 while (--num) {
123 block->next = RedisModule_Calloc(1, sizeof(struct MemBlock));
124 block = block->next;
125 }
126
127 return head;
128}
129
130long long MemBlockNum(const struct MemBlock *head) {
131 long long num = 0;
132 const struct MemBlock *block = head;
133 while (block) {
134 num++;
135 block = block->next;
136 }
137
138 return num;
139}
140
141size_t MemBlockWrite(struct MemBlock *head, long long block_index, const char *data, size_t size) {
142 size_t w_size = 0;
143 struct MemBlock *block = head;
144 while (block_index-- && block) {
145 block = block->next;
146 }
147
148 if (block) {
149 size = size > BLOCK_SIZE ? BLOCK_SIZE:size;
150 memcpy(block->block, data, size);
151 w_size += size;
152 }
153
154 return w_size;
155}
156
157int MemBlockRead(struct MemBlock *head, long long block_index, char *data, size_t size) {
158 size_t r_size = 0;
159 struct MemBlock *block = head;
160 while (block_index-- && block) {
161 block = block->next;
162 }
163
164 if (block) {
165 size = size > BLOCK_SIZE ? BLOCK_SIZE:size;
166 memcpy(data, block->block, size);
167 r_size += size;
168 }
169
170 return r_size;
171}
172
173void MemPoolFreeDb(RedisModuleCtx *ctx, int dbid) {
174 RedisModuleString *key;
175 void *tdata;
176 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(mem_pool[dbid], "^", NULL, 0);
177 while((key = RedisModule_DictNext(ctx, iter, &tdata)) != NULL) {
178 MemBlockFree((struct MemBlock *)tdata);
179 }
180 RedisModule_DictIteratorStop(iter);
181 RedisModule_FreeDict(NULL, mem_pool[dbid]);
182 mem_pool[dbid] = RedisModule_CreateDict(NULL);
183}
184
185struct MemBlock *MemBlockClone(const struct MemBlock *head) {
186 struct MemBlock *newhead = NULL;
187 if (head) {
188 newhead = RedisModule_Calloc(1, sizeof(struct MemBlock));
189 memcpy(newhead->block, head->block, BLOCK_SIZE);
190 struct MemBlock *newblock = newhead;
191 const struct MemBlock *oldblock = head->next;
192 while (oldblock) {
193 newblock->next = RedisModule_Calloc(1, sizeof(struct MemBlock));
194 newblock = newblock->next;
195 memcpy(newblock->block, oldblock->block, BLOCK_SIZE);
196 oldblock = oldblock->next;
197 }
198 }
199
200 return newhead;
201}
202
203/*---------------------------- event handler ------------------------------------*/
204void swapDbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data) {
205 REDISMODULE_NOT_USED(ctx);
206 REDISMODULE_NOT_USED(e);
207 REDISMODULE_NOT_USED(sub);
208
209 RedisModuleSwapDbInfo *ei = data;
210
211 // swap
212 RedisModuleDict *tmp = mem_pool[ei->dbnum_first];
213 mem_pool[ei->dbnum_first] = mem_pool[ei->dbnum_second];
214 mem_pool[ei->dbnum_second] = tmp;
215}
216
217void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data) {
218 REDISMODULE_NOT_USED(ctx);
219 REDISMODULE_NOT_USED(e);
220 int i;
221 RedisModuleFlushInfo *fi = data;
222
223 RedisModule_AutoMemory(ctx);
224
225 if (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) {
226 if (fi->dbnum != -1) {
227 MemPoolFreeDb(ctx, fi->dbnum);
228 } else {
229 for (i = 0; i < MAX_DB; i++) {
230 MemPoolFreeDb(ctx, i);
231 }
232 }
233 }
234}
235
236/*---------------------------- command implementation ------------------------------------*/
237
238/* MEM.ALLOC key block_num */
239int MemAlloc_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
240 RedisModule_AutoMemory(ctx);
241
242 if (argc != 3) {
243 return RedisModule_WrongArity(ctx);
244 }
245
246 long long block_num;
247 if ((RedisModule_StringToLongLong(argv[2], &block_num) != REDISMODULE_OK) || block_num <= 0) {
248 return RedisModule_ReplyWithError(ctx, "ERR invalid block_num: must be a value greater than 0");
249 }
250
251 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
252 int type = RedisModule_KeyType(key);
253 if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != MemAllocType) {
254 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
255 }
256
257 MemAllocObject *o;
258 if (type == REDISMODULE_KEYTYPE_EMPTY) {
259 o = createMemAllocObject();
260 RedisModule_ModuleTypeSetValue(key, MemAllocType, o);
261 } else {
262 o = RedisModule_ModuleTypeGetValue(key);
263 }
264
265 struct MemBlock *mem = MemBlockCreate(block_num);
266 RedisModule_Assert(mem != NULL);
267 RedisModule_DictSet(mem_pool[RedisModule_GetSelectedDb(ctx)], argv[1], mem);
268 o->size = block_num;
269 o->used = 0;
270 o->mask = 0;
271
272 RedisModule_ReplyWithLongLong(ctx, block_num);
273 RedisModule_ReplicateVerbatim(ctx);
274 return REDISMODULE_OK;
275}
276
277/* MEM.FREE key */
278int MemFree_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
279 RedisModule_AutoMemory(ctx);
280
281 if (argc != 2) {
282 return RedisModule_WrongArity(ctx);
283 }
284
285 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
286 int type = RedisModule_KeyType(key);
287 if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != MemAllocType) {
288 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
289 }
290
291 int ret = 0;
292 MemAllocObject *o;
293 if (type == REDISMODULE_KEYTYPE_EMPTY) {
294 RedisModule_ReplyWithLongLong(ctx, ret);
295 return REDISMODULE_OK;
296 } else {
297 o = RedisModule_ModuleTypeGetValue(key);
298 }
299
300 int nokey;
301 struct MemBlock *mem = (struct MemBlock *)RedisModule_DictGet(mem_pool[RedisModule_GetSelectedDb(ctx)], argv[1], &nokey);
302 if (!nokey && mem) {
303 RedisModule_DictDel(mem_pool[RedisModule_GetSelectedDb(ctx)], argv[1], NULL);
304 MemBlockFree(mem);
305 o->used = 0;
306 o->size = 0;
307 o->mask = 0;
308 ret = 1;
309 }
310
311 RedisModule_ReplyWithLongLong(ctx, ret);
312 RedisModule_ReplicateVerbatim(ctx);
313 return REDISMODULE_OK;
314}
315
316/* MEM.WRITE key block_index data */
317int MemWrite_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
318 RedisModule_AutoMemory(ctx);
319
320 if (argc != 4) {
321 return RedisModule_WrongArity(ctx);
322 }
323
324 long long block_index;
325 if ((RedisModule_StringToLongLong(argv[2], &block_index) != REDISMODULE_OK) || block_index < 0) {
326 return RedisModule_ReplyWithError(ctx, "ERR invalid block_index: must be a value greater than 0");
327 }
328
329 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
330 int type = RedisModule_KeyType(key);
331 if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != MemAllocType) {
332 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
333 }
334
335 MemAllocObject *o;
336 if (type == REDISMODULE_KEYTYPE_EMPTY) {
337 return RedisModule_ReplyWithError(ctx, "ERR Memory has not been allocated");
338 } else {
339 o = RedisModule_ModuleTypeGetValue(key);
340 }
341
342 if (o->mask & (1UL << block_index)) {
343 return RedisModule_ReplyWithError(ctx, "ERR block is busy");
344 }
345
346 int ret = 0;
347 int nokey;
348 struct MemBlock *mem = (struct MemBlock *)RedisModule_DictGet(mem_pool[RedisModule_GetSelectedDb(ctx)], argv[1], &nokey);
349 if (!nokey && mem) {
350 size_t len;
351 const char *buf = RedisModule_StringPtrLen(argv[3], &len);
352 ret = MemBlockWrite(mem, block_index, buf, len);
353 o->mask |= (1UL << block_index);
354 o->used++;
355 }
356
357 RedisModule_ReplyWithLongLong(ctx, ret);
358 RedisModule_ReplicateVerbatim(ctx);
359 return REDISMODULE_OK;
360}
361
362/* MEM.READ key block_index */
363int MemRead_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
364 RedisModule_AutoMemory(ctx);
365
366 if (argc != 3) {
367 return RedisModule_WrongArity(ctx);
368 }
369
370 long long block_index;
371 if ((RedisModule_StringToLongLong(argv[2], &block_index) != REDISMODULE_OK) || block_index < 0) {
372 return RedisModule_ReplyWithError(ctx, "ERR invalid block_index: must be a value greater than 0");
373 }
374
375 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
376 int type = RedisModule_KeyType(key);
377 if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != MemAllocType) {
378 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
379 }
380
381 MemAllocObject *o;
382 if (type == REDISMODULE_KEYTYPE_EMPTY) {
383 return RedisModule_ReplyWithError(ctx, "ERR Memory has not been allocated");
384 } else {
385 o = RedisModule_ModuleTypeGetValue(key);
386 }
387
388 if (!(o->mask & (1UL << block_index))) {
389 return RedisModule_ReplyWithNull(ctx);
390 }
391
392 int nokey;
393 struct MemBlock *mem = (struct MemBlock *)RedisModule_DictGet(mem_pool[RedisModule_GetSelectedDb(ctx)], argv[1], &nokey);
394 RedisModule_Assert(nokey == 0 && mem != NULL);
395
396 char buf[BLOCK_SIZE];
397 MemBlockRead(mem, block_index, buf, sizeof(buf));
398
399 /* Assuming that the contents are all c-style strings */
400 RedisModule_ReplyWithStringBuffer(ctx, buf, strlen(buf));
401 return REDISMODULE_OK;
402}
403
404/* MEM.USAGE dbid */
405int MemUsage_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
406 RedisModule_AutoMemory(ctx);
407
408 if (argc != 2) {
409 return RedisModule_WrongArity(ctx);
410 }
411
412 long long dbid;
413 if ((RedisModule_StringToLongLong(argv[1], (long long *)&dbid) != REDISMODULE_OK)) {
414 return RedisModule_ReplyWithError(ctx, "ERR invalid value: must be a integer");
415 }
416
417 if (dbid < 0 || dbid >= MAX_DB) {
418 return RedisModule_ReplyWithError(ctx, "ERR dbid out of range");
419 }
420
421
422 long long size = 0, used = 0;
423
424 void *data;
425 RedisModuleString *key;
426 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(mem_pool[dbid], "^", NULL, 0);
427 while((key = RedisModule_DictNext(ctx, iter, &data)) != NULL) {
428 int dbbackup = RedisModule_GetSelectedDb(ctx);
429 RedisModule_SelectDb(ctx, dbid);
430 RedisModuleKey *openkey = RedisModule_OpenKey(ctx, key, REDISMODULE_READ);
431 int type = RedisModule_KeyType(openkey);
432 RedisModule_Assert(type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(openkey) == MemAllocType);
433 MemAllocObject *o = RedisModule_ModuleTypeGetValue(openkey);
434 used += o->used;
435 size += o->size;
436 RedisModule_CloseKey(openkey);
437 RedisModule_SelectDb(ctx, dbbackup);
438 }
439 RedisModule_DictIteratorStop(iter);
440
441 RedisModule_ReplyWithArray(ctx, 4);
442 RedisModule_ReplyWithSimpleString(ctx, "total");
443 RedisModule_ReplyWithLongLong(ctx, size);
444 RedisModule_ReplyWithSimpleString(ctx, "used");
445 RedisModule_ReplyWithLongLong(ctx, used);
446 return REDISMODULE_OK;
447}
448
449/* MEM.ALLOCANDWRITE key block_num block_index data block_index data ... */
450int MemAllocAndWrite_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
451 RedisModule_AutoMemory(ctx);
452
453 if (argc < 3) {
454 return RedisModule_WrongArity(ctx);
455 }
456
457 long long block_num;
458 if ((RedisModule_StringToLongLong(argv[2], &block_num) != REDISMODULE_OK) || block_num <= 0) {
459 return RedisModule_ReplyWithError(ctx, "ERR invalid block_num: must be a value greater than 0");
460 }
461
462 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
463 int type = RedisModule_KeyType(key);
464 if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != MemAllocType) {
465 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
466 }
467
468 MemAllocObject *o;
469 if (type == REDISMODULE_KEYTYPE_EMPTY) {
470 o = createMemAllocObject();
471 RedisModule_ModuleTypeSetValue(key, MemAllocType, o);
472 } else {
473 o = RedisModule_ModuleTypeGetValue(key);
474 }
475
476 struct MemBlock *mem = MemBlockCreate(block_num);
477 RedisModule_Assert(mem != NULL);
478 RedisModule_DictSet(mem_pool[RedisModule_GetSelectedDb(ctx)], argv[1], mem);
479 o->used = 0;
480 o->mask = 0;
481 o->size = block_num;
482
483 int i = 3;
484 long long block_index;
485 for (; i < argc; i++) {
486 /* Security is guaranteed internally, so no security check. */
487 RedisModule_StringToLongLong(argv[i], &block_index);
488 size_t len;
489 const char * buf = RedisModule_StringPtrLen(argv[i + 1], &len);
490 MemBlockWrite(mem, block_index, buf, len);
491 o->used++;
492 o->mask |= (1UL << block_index);
493 }
494
495 RedisModule_ReplyWithSimpleString(ctx, "OK");
496 RedisModule_ReplicateVerbatim(ctx);
497 return REDISMODULE_OK;
498}
499
500/*---------------------------- type callbacks ------------------------------------*/
501
502void *MemAllocRdbLoad(RedisModuleIO *rdb, int encver) {
503 if (encver != 0) {
504 return NULL;
505 }
506
507 MemAllocObject *o = createMemAllocObject();
508 o->size = RedisModule_LoadSigned(rdb);
509 o->used = RedisModule_LoadSigned(rdb);
510 o->mask = RedisModule_LoadUnsigned(rdb);
511
512 const RedisModuleString *key = RedisModule_GetKeyNameFromIO(rdb);
513 int dbid = RedisModule_GetDbIdFromIO(rdb);
514
515 if (o->size) {
516 size_t size;
517 char *tmpbuf;
518 long long num = o->size;
519 struct MemBlock *head = RedisModule_Calloc(1, sizeof(struct MemBlock));
520 tmpbuf = RedisModule_LoadStringBuffer(rdb, &size);
521 memcpy(head->block, tmpbuf, size > BLOCK_SIZE ? BLOCK_SIZE:size);
522 RedisModule_Free(tmpbuf);
523 struct MemBlock *block = head;
524 while (--num) {
525 block->next = RedisModule_Calloc(1, sizeof(struct MemBlock));
526 block = block->next;
527
528 tmpbuf = RedisModule_LoadStringBuffer(rdb, &size);
529 memcpy(block->block, tmpbuf, size > BLOCK_SIZE ? BLOCK_SIZE:size);
530 RedisModule_Free(tmpbuf);
531 }
532
533 RedisModule_DictSet(mem_pool[dbid], (RedisModuleString *)key, head);
534 }
535
536 return o;
537}
538
539void MemAllocRdbSave(RedisModuleIO *rdb, void *value) {
540 MemAllocObject *o = value;
541 RedisModule_SaveSigned(rdb, o->size);
542 RedisModule_SaveSigned(rdb, o->used);
543 RedisModule_SaveUnsigned(rdb, o->mask);
544
545 const RedisModuleString *key = RedisModule_GetKeyNameFromIO(rdb);
546 int dbid = RedisModule_GetDbIdFromIO(rdb);
547
548 if (o->size) {
549 int nokey;
550 struct MemBlock *mem = (struct MemBlock *)RedisModule_DictGet(mem_pool[dbid], (RedisModuleString *)key, &nokey);
551 RedisModule_Assert(nokey == 0 && mem != NULL);
552
553 struct MemBlock *block = mem;
554 while (block) {
555 RedisModule_SaveStringBuffer(rdb, block->block, BLOCK_SIZE);
556 block = block->next;
557 }
558 }
559}
560
561void MemAllocAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) {
562 MemAllocObject *o = (MemAllocObject *)value;
563 if (o->size) {
564 int dbid = RedisModule_GetDbIdFromIO(aof);
565 int nokey;
566 size_t i = 0, j = 0;
567 struct MemBlock *mem = (struct MemBlock *)RedisModule_DictGet(mem_pool[dbid], (RedisModuleString *)key, &nokey);
568 RedisModule_Assert(nokey == 0 && mem != NULL);
569 size_t array_size = o->size * 2;
570 RedisModuleString ** string_array = RedisModule_Calloc(array_size, sizeof(RedisModuleString *));
571 while (mem) {
572 string_array[i] = RedisModule_CreateStringFromLongLong(NULL, j);
573 string_array[i + 1] = RedisModule_CreateString(NULL, mem->block, BLOCK_SIZE);
574 mem = mem->next;
575 i += 2;
576 j++;
577 }
578 RedisModule_EmitAOF(aof, "mem.allocandwrite", "slv", key, o->size, string_array, array_size);
579 for (i = 0; i < array_size; i++) {
580 RedisModule_FreeString(NULL, string_array[i]);
581 }
582 RedisModule_Free(string_array);
583 } else {
584 RedisModule_EmitAOF(aof, "mem.allocandwrite", "sl", key, o->size);
585 }
586}
587
588void MemAllocFree(void *value) {
589 RedisModule_Free(value);
590}
591
592void MemAllocUnlink(RedisModuleString *key, const void *value) {
593 REDISMODULE_NOT_USED(key);
594 REDISMODULE_NOT_USED(value);
595
596 /* When unlink and unlink2 exist at the same time, we will only call unlink2. */
597 RedisModule_Assert(0);
598}
599
600void MemAllocUnlink2(RedisModuleKeyOptCtx *ctx, const void *value) {
601 MemAllocObject *o = (MemAllocObject *)value;
602
603 const RedisModuleString *key = RedisModule_GetKeyNameFromOptCtx(ctx);
604 int dbid = RedisModule_GetDbIdFromOptCtx(ctx);
605
606 if (o->size) {
607 void *oldval;
608 RedisModule_DictDel(mem_pool[dbid], (RedisModuleString *)key, &oldval);
609 RedisModule_Assert(oldval != NULL);
610 MemBlockFree((struct MemBlock *)oldval);
611 }
612}
613
614void MemAllocDigest(RedisModuleDigest *md, void *value) {
615 MemAllocObject *o = (MemAllocObject *)value;
616 RedisModule_DigestAddLongLong(md, o->size);
617 RedisModule_DigestAddLongLong(md, o->used);
618 RedisModule_DigestAddLongLong(md, o->mask);
619
620 int dbid = RedisModule_GetDbIdFromDigest(md);
621 const RedisModuleString *key = RedisModule_GetKeyNameFromDigest(md);
622
623 if (o->size) {
624 int nokey;
625 struct MemBlock *mem = (struct MemBlock *)RedisModule_DictGet(mem_pool[dbid], (RedisModuleString *)key, &nokey);
626 RedisModule_Assert(nokey == 0 && mem != NULL);
627
628 struct MemBlock *block = mem;
629 while (block) {
630 RedisModule_DigestAddStringBuffer(md, (const char *)block->block, BLOCK_SIZE);
631 block = block->next;
632 }
633 }
634}
635
636void *MemAllocCopy2(RedisModuleKeyOptCtx *ctx, const void *value) {
637 const MemAllocObject *old = value;
638 MemAllocObject *new = createMemAllocObject();
639 new->size = old->size;
640 new->used = old->used;
641 new->mask = old->mask;
642
643 int from_dbid = RedisModule_GetDbIdFromOptCtx(ctx);
644 int to_dbid = RedisModule_GetToDbIdFromOptCtx(ctx);
645 const RedisModuleString *fromkey = RedisModule_GetKeyNameFromOptCtx(ctx);
646 const RedisModuleString *tokey = RedisModule_GetToKeyNameFromOptCtx(ctx);
647
648 if (old->size) {
649 int nokey;
650 struct MemBlock *oldmem = (struct MemBlock *)RedisModule_DictGet(mem_pool[from_dbid], (RedisModuleString *)fromkey, &nokey);
651 RedisModule_Assert(nokey == 0 && oldmem != NULL);
652 struct MemBlock *newmem = MemBlockClone(oldmem);
653 RedisModule_Assert(newmem != NULL);
654 RedisModule_DictSet(mem_pool[to_dbid], (RedisModuleString *)tokey, newmem);
655 }
656
657 return new;
658}
659
660size_t MemAllocMemUsage2(RedisModuleKeyOptCtx *ctx, const void *value, size_t sample_size) {
661 REDISMODULE_NOT_USED(ctx);
662 REDISMODULE_NOT_USED(sample_size);
663 uint64_t size = 0;
664 MemAllocObject *o = (MemAllocObject *)value;
665
666 size += sizeof(*o);
667 size += o->size * sizeof(struct MemBlock);
668
669 return size;
670}
671
672size_t MemAllocMemFreeEffort2(RedisModuleKeyOptCtx *ctx, const void *value) {
673 REDISMODULE_NOT_USED(ctx);
674 MemAllocObject *o = (MemAllocObject *)value;
675 return o->size;
676}
677
678int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
679 REDISMODULE_NOT_USED(argv);
680 REDISMODULE_NOT_USED(argc);
681
682 if (RedisModule_Init(ctx, "datatype2", 1,REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
683 return REDISMODULE_ERR;
684 }
685
686 RedisModuleTypeMethods tm = {
687 .version = REDISMODULE_TYPE_METHOD_VERSION,
688 .rdb_load = MemAllocRdbLoad,
689 .rdb_save = MemAllocRdbSave,
690 .aof_rewrite = MemAllocAofRewrite,
691 .free = MemAllocFree,
692 .digest = MemAllocDigest,
693 .unlink = MemAllocUnlink,
694 // .defrag = MemAllocDefrag, // Tested in defragtest.c
695 .unlink2 = MemAllocUnlink2,
696 .copy2 = MemAllocCopy2,
697 .mem_usage2 = MemAllocMemUsage2,
698 .free_effort2 = MemAllocMemFreeEffort2,
699 };
700
701 MemAllocType = RedisModule_CreateDataType(ctx, "mem_alloc", 0, &tm);
702 if (MemAllocType == NULL) {
703 return REDISMODULE_ERR;
704 }
705
706 if (RedisModule_CreateCommand(ctx, "mem.alloc", MemAlloc_RedisCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) {
707 return REDISMODULE_ERR;
708 }
709
710 if (RedisModule_CreateCommand(ctx, "mem.free", MemFree_RedisCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) {
711 return REDISMODULE_ERR;
712 }
713
714 if (RedisModule_CreateCommand(ctx, "mem.write", MemWrite_RedisCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) {
715 return REDISMODULE_ERR;
716 }
717
718 if (RedisModule_CreateCommand(ctx, "mem.read", MemRead_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) {
719 return REDISMODULE_ERR;
720 }
721
722 if (RedisModule_CreateCommand(ctx, "mem.usage", MemUsage_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) {
723 return REDISMODULE_ERR;
724 }
725
726 /* used for internal aof rewrite */
727 if (RedisModule_CreateCommand(ctx, "mem.allocandwrite", MemAllocAndWrite_RedisCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) {
728 return REDISMODULE_ERR;
729 }
730
731 for(int i = 0; i < MAX_DB; i++){
732 mem_pool[i] = RedisModule_CreateDict(NULL);
733 }
734
735 RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_FlushDB, flushdbCallback);
736 RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_SwapDB, swapDbCallback);
737
738 return REDISMODULE_OK;
739}
diff --git a/examples/redis-unstable/tests/modules/defragtest.c b/examples/redis-unstable/tests/modules/defragtest.c
new file mode 100644
index 0000000..8b9e182
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/defragtest.c
@@ -0,0 +1,475 @@
1/* A module that implements defrag callback mechanisms.
2 */
3
4#include "redismodule.h"
5#include <stdlib.h>
6#include <string.h>
7
8#define UNUSED(V) ((void) V)
9
10static RedisModuleType *FragType;
11
12struct FragObject {
13 unsigned long len;
14 void **values;
15 int maxstep;
16};
17
18/* Make sure we get the expected cursor */
19unsigned long int last_set_cursor = 0;
20
21unsigned long int datatype_attempts = 0;
22unsigned long int datatype_defragged = 0;
23unsigned long int datatype_raw_defragged = 0;
24unsigned long int datatype_resumes = 0;
25unsigned long int datatype_wrong_cursor = 0;
26unsigned long int defrag_started = 0;
27unsigned long int defrag_ended = 0;
28unsigned long int global_strings_attempts = 0;
29unsigned long int global_strings_defragged = 0;
30unsigned long int global_dicts_resumes = 0; /* Number of dict defragmentation resumed from a previous break */
31unsigned long int global_subdicts_resumes = 0; /* Number of subdict defragmentation resumed from a previous break */
32unsigned long int global_dicts_attempts = 0; /* Number of attempts to defragment dictionary */
33unsigned long int global_dicts_defragged = 0; /* Number of dictionaries successfully defragmented */
34unsigned long int global_dicts_items_defragged = 0; /* Number of dictionaries items successfully defragmented */
35
36unsigned long global_strings_len = 0;
37RedisModuleString **global_strings = NULL;
38
39unsigned long global_dicts_len = 0;
40RedisModuleDict **global_dicts = NULL;
41
42static void createGlobalStrings(RedisModuleCtx *ctx, unsigned long count)
43{
44 global_strings_len = count;
45 global_strings = RedisModule_Alloc(sizeof(RedisModuleString *) * count);
46
47 for (unsigned long i = 0; i < count; i++) {
48 global_strings[i] = RedisModule_CreateStringFromLongLong(ctx, i);
49 }
50}
51
52static int defragGlobalStrings(RedisModuleDefragCtx *ctx)
53{
54 unsigned long cursor = 0;
55 RedisModule_DefragCursorGet(ctx, &cursor);
56
57 if (!global_strings_len) return 0; /* strings is empty. */
58 RedisModule_Assert(cursor < global_strings_len);
59 for (; cursor < global_strings_len; cursor++) {
60 RedisModuleString *str = global_strings[cursor];
61 if (!str) continue;
62 RedisModuleString *new = RedisModule_DefragRedisModuleString(ctx, str);
63 global_strings_attempts++;
64 if (new != NULL) {
65 global_strings[cursor] = new;
66 global_strings_defragged++;
67 }
68
69 if (RedisModule_DefragShouldStop(ctx)) {
70 RedisModule_DefragCursorSet(ctx, cursor);
71 return 1;
72 }
73 }
74 return 0;
75}
76
77static void createFragGlobalStrings(RedisModuleCtx *ctx) {
78 for (unsigned long i = 0; i < global_strings_len; i++) {
79 if (i % 2 == 1) {
80 RedisModule_FreeString(ctx, global_strings[i]);
81 global_strings[i] = NULL;
82 }
83 }
84}
85
86static void createGlobalDicts(RedisModuleCtx *ctx, unsigned long count) {
87 global_dicts_len = count;
88 global_dicts = RedisModule_Alloc(sizeof(RedisModuleDict *) * count);
89
90 /* Create some nested dictionaries:
91 * - Each main dict contains some subdicts.
92 * - Each sub-dict contains some strings. */
93 for (unsigned long i = 0; i < count; i++) {
94 RedisModuleDict *dict = RedisModule_CreateDict(ctx);
95 for (unsigned long j = 0; j < 10; j++) {
96 /* Create sub dict. */
97 RedisModuleDict *subdict = RedisModule_CreateDict(ctx);
98 for (unsigned long k = 0; k < 10; k++) {
99 RedisModuleString *str = RedisModule_CreateStringFromULongLong(ctx, k);
100 RedisModule_DictSet(subdict, str, str);
101 }
102
103 RedisModuleString *key = RedisModule_CreateStringFromULongLong(ctx, j);
104 RedisModule_DictSet(dict, key, subdict);
105 RedisModule_FreeString(ctx, key);
106 }
107 global_dicts[i] = dict;
108 }
109}
110
111static void freeFragGlobalSubDict(RedisModuleCtx *ctx, RedisModuleDict *subdict) {
112 char *key;
113 size_t keylen;
114 RedisModuleString *str;
115 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(subdict, "^", NULL, 0);
116 while ((key = RedisModule_DictNextC(iter, &keylen, (void**)&str))) {
117 RedisModule_FreeString(ctx, str);
118 }
119 RedisModule_FreeDict(ctx, subdict);
120 RedisModule_DictIteratorStop(iter);
121}
122
123static void createFragGlobalDicts(RedisModuleCtx *ctx) {
124 char *key;
125 size_t keylen;
126 RedisModuleDict *subdict;
127
128 for (unsigned long i = 0; i < global_dicts_len; i++) {
129 RedisModuleDict *dict = global_dicts[i];
130 if (!dict) continue;
131
132 /* Handle dictionaries differently based on their index in global_dicts array:
133 * 1. For odd indices (i % 2 == 1): Remove the entire dictionary.
134 * 2. For even indices: Keep the dictionary but remove half of its items. */
135 if (i % 2 == 1) {
136 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(dict, "^", NULL, 0);
137 while ((key = RedisModule_DictNextC(iter, &keylen, (void**)&subdict))) {
138 freeFragGlobalSubDict(ctx, subdict);
139 }
140 RedisModule_FreeDict(ctx, dict);
141 global_dicts[i] = NULL;
142 RedisModule_DictIteratorStop(iter);
143 } else {
144 int key_index = 0;
145 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(dict, "^", NULL, 0);
146 while ((key = RedisModule_DictNextC(iter, &keylen, (void**)&subdict))) {
147 if (key_index++ % 2 == 1) {
148 freeFragGlobalSubDict(ctx, subdict);
149 RedisModule_DictReplaceC(dict, key, keylen, NULL);
150 }
151 }
152 RedisModule_DictIteratorStop(iter);
153 }
154 }
155}
156
157static int defragGlobalSubDictValueCB(RedisModuleDefragCtx *ctx, void *data, unsigned char *key, size_t keylen, void **newptr) {
158 REDISMODULE_NOT_USED(key);
159 REDISMODULE_NOT_USED(keylen);
160 if (!data) return 0;
161 *newptr = RedisModule_DefragAlloc(ctx, data);
162 return 0;
163}
164
165static int defragGlobalDictValueCB(RedisModuleDefragCtx *ctx, void *data, unsigned char *key, size_t keylen, void **newptr) {
166 REDISMODULE_NOT_USED(key);
167 REDISMODULE_NOT_USED(keylen);
168 static RedisModuleString *seekTo = NULL;
169 RedisModuleDict *subdict = data;
170 if (!subdict) return 0;
171 if (seekTo != NULL) global_subdicts_resumes++;
172
173 *newptr = RedisModule_DefragRedisModuleDict(ctx, subdict, defragGlobalSubDictValueCB, &seekTo);
174 if (*newptr) global_dicts_items_defragged++;
175 /* Return 1 if seekTo is not NULL, indicating this node needs more defrag work. */
176 return seekTo != NULL;
177}
178
179static int defragGlobalDicts(RedisModuleDefragCtx *ctx) {
180 static RedisModuleString *seekTo = NULL;
181 static unsigned long dict_index = 0;
182 unsigned long cursor = 0;
183
184 RedisModule_DefragCursorGet(ctx, &cursor);
185 if (cursor == 0) { /* Start a new defrag. */
186 if (seekTo) {
187 RedisModule_FreeString(NULL, seekTo);
188 seekTo = NULL;
189 }
190 dict_index = 0;
191 } else {
192 global_dicts_resumes++;
193 }
194
195 if (!global_dicts_len) return 0; /* dicts is empty. */
196 RedisModule_Assert(dict_index < global_dicts_len);
197 for (; dict_index < global_dicts_len; dict_index++) {
198 RedisModuleDict *dict = global_dicts[dict_index];
199 if (!dict) continue;
200 RedisModuleDict *new = RedisModule_DefragRedisModuleDict(ctx, dict, defragGlobalDictValueCB, &seekTo);
201 global_dicts_attempts++;
202 if (new != NULL) {
203 global_dicts[dict_index] = new;
204 global_dicts_defragged++;
205 }
206
207 if (seekTo != NULL) {
208 /* Set cursor to 1 to indicate defragmentation is not finished. */
209 RedisModule_DefragCursorSet(ctx, 1);
210 return 1;
211 }
212 }
213
214 /* Set cursor to 0 to indicate completion. */
215 dict_index = 0;
216 RedisModule_DefragCursorSet(ctx, 0);
217 return 0;
218}
219
220typedef enum { DEFRAG_NOT_START, DEFRAG_STRING, DEFRAG_DICT } defrag_module_stage;
221static int defragGlobal(RedisModuleDefragCtx *ctx) {
222 static defrag_module_stage stage = DEFRAG_NOT_START;
223 if (stage == DEFRAG_NOT_START) {
224 stage = DEFRAG_STRING; /* Start a new global defrag. */
225 }
226
227 if (stage == DEFRAG_STRING) {
228 if (defragGlobalStrings(ctx) != 0) return 1;
229 stage = DEFRAG_DICT;
230 }
231 if (stage == DEFRAG_DICT) {
232 if (defragGlobalDicts(ctx) != 0) return 1;
233 stage = DEFRAG_NOT_START;
234 }
235 return 0;
236}
237
238static void defragStart(RedisModuleDefragCtx *ctx) {
239 REDISMODULE_NOT_USED(ctx);
240 defrag_started++;
241}
242
243static void defragEnd(RedisModuleDefragCtx *ctx) {
244 REDISMODULE_NOT_USED(ctx);
245 defrag_ended++;
246}
247
248static void FragInfo(RedisModuleInfoCtx *ctx, int for_crash_report) {
249 REDISMODULE_NOT_USED(for_crash_report);
250
251 RedisModule_InfoAddSection(ctx, "stats");
252 RedisModule_InfoAddFieldLongLong(ctx, "datatype_attempts", datatype_attempts);
253 RedisModule_InfoAddFieldLongLong(ctx, "datatype_defragged", datatype_defragged);
254 RedisModule_InfoAddFieldLongLong(ctx, "datatype_raw_defragged", datatype_raw_defragged);
255 RedisModule_InfoAddFieldLongLong(ctx, "datatype_resumes", datatype_resumes);
256 RedisModule_InfoAddFieldLongLong(ctx, "datatype_wrong_cursor", datatype_wrong_cursor);
257 RedisModule_InfoAddFieldLongLong(ctx, "global_strings_attempts", global_strings_attempts);
258 RedisModule_InfoAddFieldLongLong(ctx, "global_strings_defragged", global_strings_defragged);
259 RedisModule_InfoAddFieldLongLong(ctx, "global_dicts_resumes", global_dicts_resumes);
260 RedisModule_InfoAddFieldLongLong(ctx, "global_subdicts_resumes", global_subdicts_resumes);
261 RedisModule_InfoAddFieldLongLong(ctx, "global_dicts_attempts", global_dicts_attempts);
262 RedisModule_InfoAddFieldLongLong(ctx, "global_dicts_defragged", global_dicts_defragged);
263 RedisModule_InfoAddFieldLongLong(ctx, "global_dicts_items_defragged", global_dicts_items_defragged);
264 RedisModule_InfoAddFieldLongLong(ctx, "defrag_started", defrag_started);
265 RedisModule_InfoAddFieldLongLong(ctx, "defrag_ended", defrag_ended);
266}
267
268struct FragObject *createFragObject(unsigned long len, unsigned long size, int maxstep) {
269 struct FragObject *o = RedisModule_Alloc(sizeof(*o));
270 o->len = len;
271 o->values = RedisModule_Alloc(sizeof(RedisModuleString*) * len);
272 o->maxstep = maxstep;
273
274 for (unsigned long i = 0; i < len; i++) {
275 o->values[i] = RedisModule_Calloc(1, size);
276 }
277
278 return o;
279}
280
281/* FRAG.RESETSTATS */
282static int fragResetStatsCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
283 REDISMODULE_NOT_USED(argv);
284 REDISMODULE_NOT_USED(argc);
285
286 datatype_attempts = 0;
287 datatype_defragged = 0;
288 datatype_raw_defragged = 0;
289 datatype_resumes = 0;
290 datatype_wrong_cursor = 0;
291 global_strings_attempts = 0;
292 global_strings_defragged = 0;
293 global_dicts_resumes = 0;
294 global_subdicts_resumes = 0;
295 global_dicts_attempts = 0;
296 global_dicts_defragged = 0;
297 global_dicts_items_defragged = 0;
298 defrag_started = 0;
299 defrag_ended = 0;
300
301 RedisModule_ReplyWithSimpleString(ctx, "OK");
302 return REDISMODULE_OK;
303}
304
305/* FRAG.CREATE key len size maxstep */
306static int fragCreateCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
307 if (argc != 5)
308 return RedisModule_WrongArity(ctx);
309
310 RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
311 REDISMODULE_READ|REDISMODULE_WRITE);
312 int type = RedisModule_KeyType(key);
313 if (type != REDISMODULE_KEYTYPE_EMPTY)
314 {
315 return RedisModule_ReplyWithError(ctx, "ERR key exists");
316 }
317
318 long long len;
319 if ((RedisModule_StringToLongLong(argv[2], &len) != REDISMODULE_OK)) {
320 return RedisModule_ReplyWithError(ctx, "ERR invalid len");
321 }
322
323 long long size;
324 if ((RedisModule_StringToLongLong(argv[3], &size) != REDISMODULE_OK)) {
325 return RedisModule_ReplyWithError(ctx, "ERR invalid size");
326 }
327
328 long long maxstep;
329 if ((RedisModule_StringToLongLong(argv[4], &maxstep) != REDISMODULE_OK)) {
330 return RedisModule_ReplyWithError(ctx, "ERR invalid maxstep");
331 }
332
333 struct FragObject *o = createFragObject(len, size, maxstep);
334 RedisModule_ModuleTypeSetValue(key, FragType, o);
335 RedisModule_ReplyWithSimpleString(ctx, "OK");
336 RedisModule_CloseKey(key);
337
338 return REDISMODULE_OK;
339}
340
341/* FRAG.create_frag_global len */
342static int fragCreateGlobalCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
343 UNUSED(argv);
344 if (argc != 2)
345 return RedisModule_WrongArity(ctx);
346
347 long long glen;
348 if ((RedisModule_StringToLongLong(argv[1], &glen) != REDISMODULE_OK)) {
349 return RedisModule_ReplyWithError(ctx, "ERR invalid len");
350 }
351
352 createGlobalStrings(ctx, glen);
353 createGlobalDicts(ctx, glen);
354 createFragGlobalStrings(ctx);
355 createFragGlobalDicts(ctx);
356 RedisModule_ReplyWithSimpleString(ctx, "OK");
357 return REDISMODULE_OK;
358}
359
360void FragFree(void *value) {
361 struct FragObject *o = value;
362
363 for (unsigned long i = 0; i < o->len; i++)
364 RedisModule_Free(o->values[i]);
365 RedisModule_Free(o->values);
366 RedisModule_Free(o);
367}
368
369size_t FragFreeEffort(RedisModuleString *key, const void *value) {
370 REDISMODULE_NOT_USED(key);
371
372 const struct FragObject *o = value;
373 return o->len;
374}
375
376int FragDefrag(RedisModuleDefragCtx *ctx, RedisModuleString *key, void **value) {
377 unsigned long i = 0;
378 int steps = 0;
379
380 int dbid = RedisModule_GetDbIdFromDefragCtx(ctx);
381 RedisModule_Assert(dbid != -1);
382
383 RedisModule_Log(NULL, "notice", "Defrag key: %s", RedisModule_StringPtrLen(key, NULL));
384
385 /* Attempt to get cursor, validate it's what we're exepcting */
386 if (RedisModule_DefragCursorGet(ctx, &i) == REDISMODULE_OK) {
387 if (i > 0) datatype_resumes++;
388
389 /* Validate we're expecting this cursor */
390 if (i != last_set_cursor) datatype_wrong_cursor++;
391 } else {
392 if (last_set_cursor != 0) datatype_wrong_cursor++;
393 }
394
395 /* Attempt to defrag the object itself */
396 datatype_attempts++;
397 struct FragObject *o = RedisModule_DefragAlloc(ctx, *value);
398 if (o == NULL) {
399 /* Not defragged */
400 o = *value;
401 } else {
402 /* Defragged */
403 *value = o;
404 datatype_defragged++;
405 }
406
407 /* Deep defrag now */
408 for (; i < o->len; i++) {
409 datatype_attempts++;
410 void *new = RedisModule_DefragAlloc(ctx, o->values[i]);
411 if (new) {
412 o->values[i] = new;
413 datatype_defragged++;
414 }
415
416 if ((o->maxstep && ++steps > o->maxstep) ||
417 ((i % 64 == 0) && RedisModule_DefragShouldStop(ctx)))
418 {
419 RedisModule_DefragCursorSet(ctx, i);
420 last_set_cursor = i;
421 return 1;
422 }
423 }
424
425 /* Defrag the values array itself using RedisModule_DefragAllocRaw
426 * and RedisModule_DefragFreeRaw for testing purposes. */
427 void *new_values = RedisModule_DefragAllocRaw(ctx, o->len * sizeof(void*));
428 memcpy(new_values, o->values, o->len * sizeof(void*));
429 RedisModule_DefragFreeRaw(ctx, o->values);
430 o->values = new_values;
431 datatype_raw_defragged++;
432
433 last_set_cursor = 0;
434 return 0;
435}
436
437int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
438 REDISMODULE_NOT_USED(argv);
439 REDISMODULE_NOT_USED(argc);
440
441 if (RedisModule_Init(ctx, "defragtest", 1, REDISMODULE_APIVER_1)
442 == REDISMODULE_ERR) return REDISMODULE_ERR;
443
444 if (RedisModule_GetTypeMethodVersion() < REDISMODULE_TYPE_METHOD_VERSION) {
445 return REDISMODULE_ERR;
446 }
447
448 RedisModuleTypeMethods tm = {
449 .version = REDISMODULE_TYPE_METHOD_VERSION,
450 .free = FragFree,
451 .free_effort = FragFreeEffort,
452 .defrag = FragDefrag
453 };
454
455 FragType = RedisModule_CreateDataType(ctx, "frag_type", 0, &tm);
456 if (FragType == NULL) return REDISMODULE_ERR;
457
458 if (RedisModule_CreateCommand(ctx, "frag.create",
459 fragCreateCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR)
460 return REDISMODULE_ERR;
461
462 if (RedisModule_CreateCommand(ctx, "frag.create_frag_global",
463 fragCreateGlobalCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR)
464 return REDISMODULE_ERR;
465
466 if (RedisModule_CreateCommand(ctx, "frag.resetstats",
467 fragResetStatsCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR)
468 return REDISMODULE_ERR;
469
470 RedisModule_RegisterInfoFunc(ctx, FragInfo);
471 RedisModule_RegisterDefragFunc2(ctx, defragGlobal);
472 RedisModule_RegisterDefragCallbacks(ctx, defragStart, defragEnd);
473
474 return REDISMODULE_OK;
475}
diff --git a/examples/redis-unstable/tests/modules/eventloop.c b/examples/redis-unstable/tests/modules/eventloop.c
new file mode 100644
index 0000000..c0cfdf0
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/eventloop.c
@@ -0,0 +1,276 @@
1/* This module contains four tests :
2 * 1- test.sanity : Basic tests for argument validation mostly.
3 * 2- test.sendbytes : Creates a pipe and registers its fds to the event loop,
4 * one end of the pipe for read events and the other end for
5 * the write events. On writable event, data is written. On
6 * readable event data is read. Repeated until all data is
7 * received.
8 * 3- test.iteration : A test for BEFORE_SLEEP and AFTER_SLEEP callbacks.
9 * Counters are incremented each time these events are
10 * fired. They should be equal and increment monotonically.
11 * 4- test.oneshot : Test for oneshot API
12 */
13
14#include "redismodule.h"
15#include <stdlib.h>
16#include <unistd.h>
17#include <fcntl.h>
18#include <memory.h>
19#include <errno.h>
20
21int fds[2];
22long long buf_size;
23char *src;
24long long src_offset;
25char *dst;
26long long dst_offset;
27
28RedisModuleBlockedClient *bc;
29RedisModuleCtx *reply_ctx;
30
31void onReadable(int fd, void *user_data, int mask) {
32 REDISMODULE_NOT_USED(mask);
33
34 RedisModule_Assert(strcmp(user_data, "userdataread") == 0);
35
36 while (1) {
37 int rd = read(fd, dst + dst_offset, buf_size - dst_offset);
38 if (rd <= 0)
39 return;
40 dst_offset += rd;
41
42 /* Received all bytes */
43 if (dst_offset == buf_size) {
44 if (memcmp(src, dst, buf_size) == 0)
45 RedisModule_ReplyWithSimpleString(reply_ctx, "OK");
46 else
47 RedisModule_ReplyWithError(reply_ctx, "ERR bytes mismatch");
48
49 RedisModule_EventLoopDel(fds[0], REDISMODULE_EVENTLOOP_READABLE);
50 RedisModule_EventLoopDel(fds[1], REDISMODULE_EVENTLOOP_WRITABLE);
51 RedisModule_Free(src);
52 RedisModule_Free(dst);
53 close(fds[0]);
54 close(fds[1]);
55
56 RedisModule_FreeThreadSafeContext(reply_ctx);
57 RedisModule_UnblockClient(bc, NULL);
58 return;
59 }
60 };
61}
62
63void onWritable(int fd, void *user_data, int mask) {
64 REDISMODULE_NOT_USED(user_data);
65 REDISMODULE_NOT_USED(mask);
66
67 RedisModule_Assert(strcmp(user_data, "userdatawrite") == 0);
68
69 while (1) {
70 /* Check if we sent all data */
71 if (src_offset >= buf_size)
72 return;
73 int written = write(fd, src + src_offset, buf_size - src_offset);
74 if (written <= 0) {
75 return;
76 }
77
78 src_offset += written;
79 };
80}
81
82/* Create a pipe(), register pipe fds to the event loop and send/receive data
83 * using them. */
84int sendbytes(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
85 if (argc != 2) {
86 RedisModule_WrongArity(ctx);
87 return REDISMODULE_OK;
88 }
89
90 if (RedisModule_StringToLongLong(argv[1], &buf_size) != REDISMODULE_OK ||
91 buf_size == 0) {
92 RedisModule_ReplyWithError(ctx, "Invalid integer value");
93 return REDISMODULE_OK;
94 }
95
96 bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
97 reply_ctx = RedisModule_GetThreadSafeContext(bc);
98
99 /* Allocate source buffer and write some random data */
100 src = RedisModule_Calloc(1,buf_size);
101 src_offset = 0;
102 memset(src, rand() % 0xFF, buf_size);
103 memcpy(src, "randomtestdata", strlen("randomtestdata"));
104
105 dst = RedisModule_Calloc(1,buf_size);
106 dst_offset = 0;
107
108 /* Create a pipe and register it to the event loop. */
109 if (pipe(fds) < 0) return REDISMODULE_ERR;
110 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) < 0) return REDISMODULE_ERR;
111 if (fcntl(fds[1], F_SETFL, O_NONBLOCK) < 0) return REDISMODULE_ERR;
112
113 if (RedisModule_EventLoopAdd(fds[0], REDISMODULE_EVENTLOOP_READABLE,
114 onReadable, "userdataread") != REDISMODULE_OK) return REDISMODULE_ERR;
115 if (RedisModule_EventLoopAdd(fds[1], REDISMODULE_EVENTLOOP_WRITABLE,
116 onWritable, "userdatawrite") != REDISMODULE_OK) return REDISMODULE_ERR;
117 return REDISMODULE_OK;
118}
119
120int sanity(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
121 REDISMODULE_NOT_USED(argv);
122 REDISMODULE_NOT_USED(argc);
123
124 if (pipe(fds) < 0) return REDISMODULE_ERR;
125
126 if (RedisModule_EventLoopAdd(fds[0], 9999999, onReadable, NULL)
127 == REDISMODULE_OK || errno != EINVAL) {
128 RedisModule_ReplyWithError(ctx, "ERR non-existing event type should fail");
129 goto out;
130 }
131 if (RedisModule_EventLoopAdd(-1, REDISMODULE_EVENTLOOP_READABLE, onReadable, NULL)
132 == REDISMODULE_OK || errno != ERANGE) {
133 RedisModule_ReplyWithError(ctx, "ERR out of range fd should fail");
134 goto out;
135 }
136 if (RedisModule_EventLoopAdd(99999999, REDISMODULE_EVENTLOOP_READABLE, onReadable, NULL)
137 == REDISMODULE_OK || errno != ERANGE) {
138 RedisModule_ReplyWithError(ctx, "ERR out of range fd should fail");
139 goto out;
140 }
141 if (RedisModule_EventLoopAdd(fds[0], REDISMODULE_EVENTLOOP_READABLE, NULL, NULL)
142 == REDISMODULE_OK || errno != EINVAL) {
143 RedisModule_ReplyWithError(ctx, "ERR null callback should fail");
144 goto out;
145 }
146 if (RedisModule_EventLoopAdd(fds[0], 9999999, onReadable, NULL)
147 == REDISMODULE_OK || errno != EINVAL) {
148 RedisModule_ReplyWithError(ctx, "ERR non-existing event type should fail");
149 goto out;
150 }
151 if (RedisModule_EventLoopDel(fds[0], REDISMODULE_EVENTLOOP_READABLE)
152 != REDISMODULE_OK || errno != 0) {
153 RedisModule_ReplyWithError(ctx, "ERR del on non-registered fd should not fail");
154 goto out;
155 }
156 if (RedisModule_EventLoopDel(fds[0], 9999999) == REDISMODULE_OK ||
157 errno != EINVAL) {
158 RedisModule_ReplyWithError(ctx, "ERR non-existing event type should fail");
159 goto out;
160 }
161 if (RedisModule_EventLoopDel(-1, REDISMODULE_EVENTLOOP_READABLE)
162 == REDISMODULE_OK || errno != ERANGE) {
163 RedisModule_ReplyWithError(ctx, "ERR out of range fd should fail");
164 goto out;
165 }
166 if (RedisModule_EventLoopDel(99999999, REDISMODULE_EVENTLOOP_READABLE)
167 == REDISMODULE_OK || errno != ERANGE) {
168 RedisModule_ReplyWithError(ctx, "ERR out of range fd should fail");
169 goto out;
170 }
171 if (RedisModule_EventLoopAdd(fds[0], REDISMODULE_EVENTLOOP_READABLE, onReadable, NULL)
172 != REDISMODULE_OK || errno != 0) {
173 RedisModule_ReplyWithError(ctx, "ERR Add failed");
174 goto out;
175 }
176 if (RedisModule_EventLoopAdd(fds[0], REDISMODULE_EVENTLOOP_READABLE, onReadable, NULL)
177 != REDISMODULE_OK || errno != 0) {
178 RedisModule_ReplyWithError(ctx, "ERR Adding same fd twice failed");
179 goto out;
180 }
181 if (RedisModule_EventLoopDel(fds[0], REDISMODULE_EVENTLOOP_READABLE)
182 != REDISMODULE_OK || errno != 0) {
183 RedisModule_ReplyWithError(ctx, "ERR Del failed");
184 goto out;
185 }
186 if (RedisModule_EventLoopAddOneShot(NULL, NULL) == REDISMODULE_OK || errno != EINVAL) {
187 RedisModule_ReplyWithError(ctx, "ERR null callback should fail");
188 goto out;
189 }
190
191 RedisModule_ReplyWithSimpleString(ctx, "OK");
192out:
193 close(fds[0]);
194 close(fds[1]);
195 return REDISMODULE_OK;
196}
197
198static long long beforeSleepCount;
199static long long afterSleepCount;
200
201int iteration(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
202 REDISMODULE_NOT_USED(argv);
203 REDISMODULE_NOT_USED(argc);
204 /* On each event loop iteration, eventloopCallback() is called. We increment
205 * beforeSleepCount and afterSleepCount, so these two should be equal.
206 * We reply with iteration count, caller can test if iteration count
207 * increments monotonically */
208 RedisModule_Assert(beforeSleepCount == afterSleepCount);
209 RedisModule_ReplyWithLongLong(ctx, beforeSleepCount);
210 return REDISMODULE_OK;
211}
212
213void oneshotCallback(void* arg)
214{
215 RedisModule_Assert(strcmp(arg, "userdata") == 0);
216 RedisModule_ReplyWithSimpleString(reply_ctx, "OK");
217 RedisModule_FreeThreadSafeContext(reply_ctx);
218 RedisModule_UnblockClient(bc, NULL);
219}
220
221int oneshot(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
222 REDISMODULE_NOT_USED(argv);
223 REDISMODULE_NOT_USED(argc);
224
225 bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
226 reply_ctx = RedisModule_GetThreadSafeContext(bc);
227
228 if (RedisModule_EventLoopAddOneShot(oneshotCallback, "userdata") != REDISMODULE_OK) {
229 RedisModule_ReplyWithError(ctx, "ERR oneshot failed");
230 RedisModule_FreeThreadSafeContext(reply_ctx);
231 RedisModule_UnblockClient(bc, NULL);
232 }
233 return REDISMODULE_OK;
234}
235
236void eventloopCallback(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data) {
237 REDISMODULE_NOT_USED(ctx);
238 REDISMODULE_NOT_USED(eid);
239 REDISMODULE_NOT_USED(subevent);
240 REDISMODULE_NOT_USED(data);
241
242 RedisModule_Assert(eid.id == REDISMODULE_EVENT_EVENTLOOP);
243 if (subevent == REDISMODULE_SUBEVENT_EVENTLOOP_BEFORE_SLEEP)
244 beforeSleepCount++;
245 else if (subevent == REDISMODULE_SUBEVENT_EVENTLOOP_AFTER_SLEEP)
246 afterSleepCount++;
247}
248
249int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
250 REDISMODULE_NOT_USED(argv);
251 REDISMODULE_NOT_USED(argc);
252
253 if (RedisModule_Init(ctx,"eventloop",1,REDISMODULE_APIVER_1)
254 == REDISMODULE_ERR) return REDISMODULE_ERR;
255
256 /* Test basics. */
257 if (RedisModule_CreateCommand(ctx, "test.sanity", sanity, "", 0, 0, 0)
258 == REDISMODULE_ERR) return REDISMODULE_ERR;
259
260 /* Register a command to create a pipe() and send data through it by using
261 * event loop API. */
262 if (RedisModule_CreateCommand(ctx, "test.sendbytes", sendbytes, "", 0, 0, 0)
263 == REDISMODULE_ERR) return REDISMODULE_ERR;
264
265 /* Register a command to return event loop iteration count. */
266 if (RedisModule_CreateCommand(ctx, "test.iteration", iteration, "", 0, 0, 0)
267 == REDISMODULE_ERR) return REDISMODULE_ERR;
268
269 if (RedisModule_CreateCommand(ctx, "test.oneshot", oneshot, "", 0, 0, 0)
270 == REDISMODULE_ERR) return REDISMODULE_ERR;
271
272 if (RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_EventLoop,
273 eventloopCallback) != REDISMODULE_OK) return REDISMODULE_ERR;
274
275 return REDISMODULE_OK;
276}
diff --git a/examples/redis-unstable/tests/modules/fork.c b/examples/redis-unstable/tests/modules/fork.c
new file mode 100644
index 0000000..d7a0d15
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/fork.c
@@ -0,0 +1,96 @@
1
2/* define macros for having usleep */
3#define _BSD_SOURCE
4#define _DEFAULT_SOURCE
5
6#include "redismodule.h"
7#include <string.h>
8#include <assert.h>
9#include <unistd.h>
10
11#define UNUSED(V) ((void) V)
12
13int child_pid = -1;
14int exitted_with_code = -1;
15
16void done_handler(int exitcode, int bysignal, void *user_data) {
17 child_pid = -1;
18 exitted_with_code = exitcode;
19 assert(user_data==(void*)0xdeadbeef);
20 UNUSED(bysignal);
21}
22
23int fork_create(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
24{
25 long long code_to_exit_with;
26 long long usleep_us;
27 if (argc != 3) {
28 RedisModule_WrongArity(ctx);
29 return REDISMODULE_OK;
30 }
31
32 if(!RMAPI_FUNC_SUPPORTED(RedisModule_Fork)){
33 RedisModule_ReplyWithError(ctx, "Fork api is not supported in the current redis version");
34 return REDISMODULE_OK;
35 }
36
37 RedisModule_StringToLongLong(argv[1], &code_to_exit_with);
38 RedisModule_StringToLongLong(argv[2], &usleep_us);
39 exitted_with_code = -1;
40 int fork_child_pid = RedisModule_Fork(done_handler, (void*)0xdeadbeef);
41 if (fork_child_pid < 0) {
42 RedisModule_ReplyWithError(ctx, "Fork failed");
43 return REDISMODULE_OK;
44 } else if (fork_child_pid > 0) {
45 /* parent */
46 child_pid = fork_child_pid;
47 RedisModule_ReplyWithLongLong(ctx, child_pid);
48 return REDISMODULE_OK;
49 }
50
51 /* child */
52 RedisModule_Log(ctx, "notice", "fork child started");
53 usleep(usleep_us);
54 RedisModule_Log(ctx, "notice", "fork child exiting");
55 RedisModule_ExitFromChild(code_to_exit_with);
56 /* unreachable */
57 return 0;
58}
59
60int fork_exitcode(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
61{
62 UNUSED(argv);
63 UNUSED(argc);
64 RedisModule_ReplyWithLongLong(ctx, exitted_with_code);
65 return REDISMODULE_OK;
66}
67
68int fork_kill(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
69{
70 UNUSED(argv);
71 UNUSED(argc);
72 if (RedisModule_KillForkChild(child_pid) != REDISMODULE_OK)
73 RedisModule_ReplyWithError(ctx, "KillForkChild failed");
74 else
75 RedisModule_ReplyWithLongLong(ctx, 1);
76 child_pid = -1;
77 return REDISMODULE_OK;
78}
79
80int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
81 UNUSED(argv);
82 UNUSED(argc);
83 if (RedisModule_Init(ctx,"fork",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
84 return REDISMODULE_ERR;
85
86 if (RedisModule_CreateCommand(ctx,"fork.create", fork_create,"",0,0,0) == REDISMODULE_ERR)
87 return REDISMODULE_ERR;
88
89 if (RedisModule_CreateCommand(ctx,"fork.exitcode", fork_exitcode,"",0,0,0) == REDISMODULE_ERR)
90 return REDISMODULE_ERR;
91
92 if (RedisModule_CreateCommand(ctx,"fork.kill", fork_kill,"",0,0,0) == REDISMODULE_ERR)
93 return REDISMODULE_ERR;
94
95 return REDISMODULE_OK;
96}
diff --git a/examples/redis-unstable/tests/modules/getchannels.c b/examples/redis-unstable/tests/modules/getchannels.c
new file mode 100644
index 0000000..330531d
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/getchannels.c
@@ -0,0 +1,69 @@
1#include "redismodule.h"
2#include <strings.h>
3#include <assert.h>
4#include <unistd.h>
5#include <errno.h>
6
7/* A sample with declarable channels, that are used to validate against ACLs */
8int getChannels_subscribe(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
9 if ((argc - 1) % 3 != 0) {
10 RedisModule_WrongArity(ctx);
11 return REDISMODULE_OK;
12 }
13 char *err = NULL;
14
15 /* getchannels.command [[subscribe|unsubscribe|publish] [pattern|literal] <channel> ...]
16 * This command marks the given channel is accessed based on the
17 * provided modifiers. */
18 for (int i = 1; i < argc; i += 3) {
19 const char *operation = RedisModule_StringPtrLen(argv[i], NULL);
20 const char *type = RedisModule_StringPtrLen(argv[i+1], NULL);
21 int flags = 0;
22
23 if (!strcasecmp(operation, "subscribe")) {
24 flags |= REDISMODULE_CMD_CHANNEL_SUBSCRIBE;
25 } else if (!strcasecmp(operation, "unsubscribe")) {
26 flags |= REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE;
27 } else if (!strcasecmp(operation, "publish")) {
28 flags |= REDISMODULE_CMD_CHANNEL_PUBLISH;
29 } else {
30 err = "Invalid channel operation";
31 break;
32 }
33
34 if (!strcasecmp(type, "literal")) {
35 /* No op */
36 } else if (!strcasecmp(type, "pattern")) {
37 flags |= REDISMODULE_CMD_CHANNEL_PATTERN;
38 } else {
39 err = "Invalid channel type";
40 break;
41 }
42 if (RedisModule_IsChannelsPositionRequest(ctx)) {
43 RedisModule_ChannelAtPosWithFlags(ctx, i+2, flags);
44 }
45 }
46
47 if (!RedisModule_IsChannelsPositionRequest(ctx)) {
48 if (err) {
49 RedisModule_ReplyWithError(ctx, err);
50 } else {
51 /* Normal implementation would go here, but for tests just return okay */
52 RedisModule_ReplyWithSimpleString(ctx, "OK");
53 }
54 }
55
56 return REDISMODULE_OK;
57}
58
59int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
60 REDISMODULE_NOT_USED(argv);
61 REDISMODULE_NOT_USED(argc);
62 if (RedisModule_Init(ctx, "getchannels", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
63 return REDISMODULE_ERR;
64
65 if (RedisModule_CreateCommand(ctx, "getchannels.command", getChannels_subscribe, "getchannels-api", 0, 0, 0) == REDISMODULE_ERR)
66 return REDISMODULE_ERR;
67
68 return REDISMODULE_OK;
69}
diff --git a/examples/redis-unstable/tests/modules/getkeys.c b/examples/redis-unstable/tests/modules/getkeys.c
new file mode 100644
index 0000000..cee3b3e
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/getkeys.c
@@ -0,0 +1,178 @@
1
2#include "redismodule.h"
3#include <strings.h>
4#include <assert.h>
5#include <unistd.h>
6#include <errno.h>
7
8#define UNUSED(V) ((void) V)
9
10/* A sample movable keys command that returns a list of all
11 * arguments that follow a KEY argument, i.e.
12 */
13int getkeys_command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
14{
15 int i;
16 int count = 0;
17
18 /* Handle getkeys-api introspection */
19 if (RedisModule_IsKeysPositionRequest(ctx)) {
20 for (i = 0; i < argc; i++) {
21 size_t len;
22 const char *str = RedisModule_StringPtrLen(argv[i], &len);
23
24 if (len == 3 && !strncasecmp(str, "key", 3) && i + 1 < argc)
25 RedisModule_KeyAtPos(ctx, i + 1);
26 }
27
28 return REDISMODULE_OK;
29 }
30
31 /* Handle real command invocation */
32 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_LEN);
33 for (i = 0; i < argc; i++) {
34 size_t len;
35 const char *str = RedisModule_StringPtrLen(argv[i], &len);
36
37 if (len == 3 && !strncasecmp(str, "key", 3) && i + 1 < argc) {
38 RedisModule_ReplyWithString(ctx, argv[i+1]);
39 count++;
40 }
41 }
42 RedisModule_ReplySetArrayLength(ctx, count);
43
44 return REDISMODULE_OK;
45}
46
47int getkeys_command_with_flags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
48{
49 int i;
50 int count = 0;
51
52 /* Handle getkeys-api introspection */
53 if (RedisModule_IsKeysPositionRequest(ctx)) {
54 for (i = 0; i < argc; i++) {
55 size_t len;
56 const char *str = RedisModule_StringPtrLen(argv[i], &len);
57
58 if (len == 3 && !strncasecmp(str, "key", 3) && i + 1 < argc)
59 RedisModule_KeyAtPosWithFlags(ctx, i + 1, REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS);
60 }
61
62 return REDISMODULE_OK;
63 }
64
65 /* Handle real command invocation */
66 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_LEN);
67 for (i = 0; i < argc; i++) {
68 size_t len;
69 const char *str = RedisModule_StringPtrLen(argv[i], &len);
70
71 if (len == 3 && !strncasecmp(str, "key", 3) && i + 1 < argc) {
72 RedisModule_ReplyWithString(ctx, argv[i+1]);
73 count++;
74 }
75 }
76 RedisModule_ReplySetArrayLength(ctx, count);
77
78 return REDISMODULE_OK;
79}
80
81int getkeys_fixed(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
82{
83 int i;
84
85 RedisModule_ReplyWithArray(ctx, argc - 1);
86 for (i = 1; i < argc; i++) {
87 RedisModule_ReplyWithString(ctx, argv[i]);
88 }
89 return REDISMODULE_OK;
90}
91
92/* Introspect a command using RM_GetCommandKeys() and returns the list
93 * of keys. Essentially this is COMMAND GETKEYS implemented in a module.
94 * INTROSPECT <with-flags> <cmd> <args>
95 */
96int getkeys_introspect(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
97{
98 long long with_flags = 0;
99
100 if (argc < 4) {
101 RedisModule_WrongArity(ctx);
102 return REDISMODULE_OK;
103 }
104
105 if (RedisModule_StringToLongLong(argv[1],&with_flags) != REDISMODULE_OK)
106 return RedisModule_ReplyWithError(ctx,"ERR invalid integer");
107
108 int num_keys, *keyflags = NULL;
109 int *keyidx = RedisModule_GetCommandKeysWithFlags(ctx, &argv[2], argc - 2, &num_keys, with_flags ? &keyflags : NULL);
110
111 if (!keyidx) {
112 if (!errno)
113 RedisModule_ReplyWithEmptyArray(ctx);
114 else {
115 char err[100];
116 switch (errno) {
117 case ENOENT:
118 RedisModule_ReplyWithError(ctx, "ERR ENOENT");
119 break;
120 case EINVAL:
121 RedisModule_ReplyWithError(ctx, "ERR EINVAL");
122 break;
123 default:
124 snprintf(err, sizeof(err) - 1, "ERR errno=%d", errno);
125 RedisModule_ReplyWithError(ctx, err);
126 break;
127 }
128 }
129 } else {
130 int i;
131
132 RedisModule_ReplyWithArray(ctx, num_keys);
133 for (i = 0; i < num_keys; i++) {
134 if (!with_flags) {
135 RedisModule_ReplyWithString(ctx, argv[2 + keyidx[i]]);
136 continue;
137 }
138 RedisModule_ReplyWithArray(ctx, 2);
139 RedisModule_ReplyWithString(ctx, argv[2 + keyidx[i]]);
140 char* sflags = "";
141 if (keyflags[i] & REDISMODULE_CMD_KEY_RO)
142 sflags = "RO";
143 else if (keyflags[i] & REDISMODULE_CMD_KEY_RW)
144 sflags = "RW";
145 else if (keyflags[i] & REDISMODULE_CMD_KEY_OW)
146 sflags = "OW";
147 else if (keyflags[i] & REDISMODULE_CMD_KEY_RM)
148 sflags = "RM";
149 RedisModule_ReplyWithCString(ctx, sflags);
150 }
151
152 RedisModule_Free(keyidx);
153 RedisModule_Free(keyflags);
154 }
155
156 return REDISMODULE_OK;
157}
158
159int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
160 UNUSED(argv);
161 UNUSED(argc);
162 if (RedisModule_Init(ctx,"getkeys",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
163 return REDISMODULE_ERR;
164
165 if (RedisModule_CreateCommand(ctx,"getkeys.command", getkeys_command,"getkeys-api",0,0,0) == REDISMODULE_ERR)
166 return REDISMODULE_ERR;
167
168 if (RedisModule_CreateCommand(ctx,"getkeys.command_with_flags", getkeys_command_with_flags,"getkeys-api",0,0,0) == REDISMODULE_ERR)
169 return REDISMODULE_ERR;
170
171 if (RedisModule_CreateCommand(ctx,"getkeys.fixed", getkeys_fixed,"",2,4,1) == REDISMODULE_ERR)
172 return REDISMODULE_ERR;
173
174 if (RedisModule_CreateCommand(ctx,"getkeys.introspect", getkeys_introspect,"",0,0,0) == REDISMODULE_ERR)
175 return REDISMODULE_ERR;
176
177 return REDISMODULE_OK;
178}
diff --git a/examples/redis-unstable/tests/modules/hash.c b/examples/redis-unstable/tests/modules/hash.c
new file mode 100644
index 0000000..462c21e
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/hash.c
@@ -0,0 +1,244 @@
1#include "redismodule.h"
2#include <strings.h>
3#include <errno.h>
4#include <stdlib.h>
5
6#define UNUSED(x) (void)(x)
7
8/* If a string is ":deleted:", the special value for deleted hash fields is
9 * returned; otherwise the input string is returned. */
10static RedisModuleString *value_or_delete(RedisModuleString *s) {
11 if (!strcasecmp(RedisModule_StringPtrLen(s, NULL), ":delete:"))
12 return REDISMODULE_HASH_DELETE;
13 else
14 return s;
15}
16
17/* HASH.SET key flags field1 value1 [field2 value2 ..]
18 *
19 * Sets 1-4 fields. Returns the same as RedisModule_HashSet().
20 * Flags is a string of "nxa" where n = NX, x = XX, a = COUNT_ALL.
21 * To delete a field, use the value ":delete:".
22 */
23int hash_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
24 if (argc < 5 || argc % 2 == 0 || argc > 11)
25 return RedisModule_WrongArity(ctx);
26
27 RedisModule_AutoMemory(ctx);
28 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
29
30 size_t flags_len;
31 const char *flags_str = RedisModule_StringPtrLen(argv[2], &flags_len);
32 int flags = REDISMODULE_HASH_NONE;
33 for (size_t i = 0; i < flags_len; i++) {
34 switch (flags_str[i]) {
35 case 'n': flags |= REDISMODULE_HASH_NX; break;
36 case 'x': flags |= REDISMODULE_HASH_XX; break;
37 case 'a': flags |= REDISMODULE_HASH_COUNT_ALL; break;
38 }
39 }
40
41 /* Test some varargs. (In real-world, use a loop and set one at a time.) */
42 int result;
43 errno = 0;
44 if (argc == 5) {
45 result = RedisModule_HashSet(key, flags,
46 argv[3], value_or_delete(argv[4]),
47 NULL);
48 } else if (argc == 7) {
49 result = RedisModule_HashSet(key, flags,
50 argv[3], value_or_delete(argv[4]),
51 argv[5], value_or_delete(argv[6]),
52 NULL);
53 } else if (argc == 9) {
54 result = RedisModule_HashSet(key, flags,
55 argv[3], value_or_delete(argv[4]),
56 argv[5], value_or_delete(argv[6]),
57 argv[7], value_or_delete(argv[8]),
58 NULL);
59 } else if (argc == 11) {
60 result = RedisModule_HashSet(key, flags,
61 argv[3], value_or_delete(argv[4]),
62 argv[5], value_or_delete(argv[6]),
63 argv[7], value_or_delete(argv[8]),
64 argv[9], value_or_delete(argv[10]),
65 NULL);
66 } else {
67 return RedisModule_ReplyWithError(ctx, "ERR too many fields");
68 }
69
70 /* Check errno */
71 if (result == 0) {
72 if (errno == ENOTSUP)
73 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
74 else
75 RedisModule_Assert(errno == ENOENT);
76 }
77
78 return RedisModule_ReplyWithLongLong(ctx, result);
79}
80
81RedisModuleKey* openKeyWithMode(RedisModuleCtx *ctx, RedisModuleString *keyName, int mode) {
82 int supportedMode = RedisModule_GetOpenKeyModesAll();
83 if (!(supportedMode & REDISMODULE_READ) || ((supportedMode & mode)!=mode)) {
84 RedisModule_ReplyWithError(ctx, "OpenKey mode is not supported");
85 return NULL;
86 }
87
88 RedisModuleKey *key = RedisModule_OpenKey(ctx, keyName, REDISMODULE_READ | mode);
89 if (!key) {
90 RedisModule_ReplyWithError(ctx, "key not found");
91 return NULL;
92 }
93
94 return key;
95}
96
97int test_open_key_subexpired_hget(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
98 if (argc<3) {
99 RedisModule_WrongArity(ctx);
100 return REDISMODULE_OK;
101 }
102
103 RedisModuleKey *key = openKeyWithMode(ctx, argv[1], REDISMODULE_OPEN_KEY_ACCESS_EXPIRED);
104 if (!key) return REDISMODULE_OK;
105
106 RedisModuleString *value;
107 RedisModule_HashGet(key,REDISMODULE_HASH_NONE,argv[2],&value,NULL);
108
109 /* return the value */
110 if (value) {
111 RedisModule_ReplyWithString(ctx, value);
112 RedisModule_FreeString(ctx, value);
113 } else {
114 RedisModule_ReplyWithNull(ctx);
115 }
116 RedisModule_CloseKey(key);
117 return REDISMODULE_OK;
118}
119
120int test_open_key_hget_expire(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
121 if (argc<3) {
122 RedisModule_WrongArity(ctx);
123 return REDISMODULE_OK;
124 }
125
126 RedisModuleKey *key = openKeyWithMode(ctx, argv[1], REDISMODULE_OPEN_KEY_ACCESS_EXPIRED);
127 if (!key) return REDISMODULE_OK;
128
129 mstime_t expireAt;
130
131 /* Let's test here that we get error if using invalid flags combination */
132 RedisModule_Assert(
133 RedisModule_HashGet(key,
134 REDISMODULE_HASH_EXISTS |
135 REDISMODULE_HASH_EXPIRE_TIME,
136 argv[2], &expireAt, NULL) == REDISMODULE_ERR);
137
138 /* Now let's get the expire time */
139 RedisModule_HashGet(key, REDISMODULE_HASH_EXPIRE_TIME,argv[2],&expireAt,NULL);
140 RedisModule_ReplyWithLongLong(ctx, expireAt);
141 RedisModule_CloseKey(key);
142 return REDISMODULE_OK;
143}
144
145/* Test variadic function to get two expiration times */
146int test_open_key_hget_two_expire(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
147 if (argc<3) {
148 RedisModule_WrongArity(ctx);
149 return REDISMODULE_OK;
150 }
151
152 RedisModuleKey *key = openKeyWithMode(ctx, argv[1], REDISMODULE_OPEN_KEY_ACCESS_EXPIRED);
153 if (!key) return REDISMODULE_OK;
154
155 mstime_t expireAt1, expireAt2;
156 RedisModule_HashGet(key,REDISMODULE_HASH_EXPIRE_TIME,argv[2],&expireAt1,argv[3],&expireAt2,NULL);
157
158 /* return the two expire time */
159 RedisModule_ReplyWithArray(ctx, 2);
160 RedisModule_ReplyWithLongLong(ctx, expireAt1);
161 RedisModule_ReplyWithLongLong(ctx, expireAt2);
162 RedisModule_CloseKey(key);
163 return REDISMODULE_OK;
164}
165
166int test_open_key_hget_min_expire(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
167 if (argc!=2) {
168 RedisModule_WrongArity(ctx);
169 return REDISMODULE_OK;
170 }
171
172 RedisModuleKey *key = openKeyWithMode(ctx, argv[1], REDISMODULE_READ);
173 if (!key) return REDISMODULE_OK;
174
175 volatile mstime_t minExpire = RedisModule_HashFieldMinExpire(key);
176 RedisModule_ReplyWithLongLong(ctx, minExpire);
177 RedisModule_CloseKey(key);
178 return REDISMODULE_OK;
179}
180
181int numReplies;
182void ScanCallback(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata) {
183 UNUSED(key);
184 RedisModuleCtx *ctx = (RedisModuleCtx *)privdata;
185
186 /* Reply with the field and value (or NULL for sets) */
187 RedisModule_ReplyWithString(ctx, field);
188 if (value) {
189 RedisModule_ReplyWithString(ctx, value);
190 } else {
191 RedisModule_ReplyWithCString(ctx, "(null)");
192 }
193 numReplies+=2;
194}
195
196int test_open_key_access_expired_hscan(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
197 if (argc < 2) {
198 RedisModule_WrongArity(ctx);
199 return REDISMODULE_OK;
200 }
201
202 RedisModuleKey *key = openKeyWithMode(ctx, argv[1], REDISMODULE_OPEN_KEY_ACCESS_EXPIRED);
203
204 if (!key)
205 return RedisModule_ReplyWithError(ctx, "ERR key not exists");
206
207 /* Verify it is a hash */
208 if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_HASH) {
209 RedisModule_CloseKey(key);
210 return RedisModule_ReplyWithError(ctx, "ERR key is not a hash");
211 }
212
213 /* Scan the hash and reply pairs of key-value */
214 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
215 numReplies = 0;
216 RedisModuleScanCursor *cursor = RedisModule_ScanCursorCreate();
217 while (RedisModule_ScanKey(key, cursor, ScanCallback, ctx));
218 RedisModule_ScanCursorDestroy(cursor);
219 RedisModule_CloseKey(key);
220 RedisModule_ReplySetArrayLength(ctx, numReplies);
221 return REDISMODULE_OK;
222}
223
224int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
225 REDISMODULE_NOT_USED(argv);
226 REDISMODULE_NOT_USED(argc);
227 if (RedisModule_Init(ctx, "hash", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
228 return REDISMODULE_ERR;
229
230 if (RedisModule_CreateCommand(ctx, "hash.set", hash_set, "write", 1, 1, 1) == REDISMODULE_ERR)
231 return REDISMODULE_ERR;
232 if (RedisModule_CreateCommand(ctx, "hash.hget_expired", test_open_key_subexpired_hget,"", 0, 0, 0) == REDISMODULE_ERR)
233 return REDISMODULE_ERR;
234 if (RedisModule_CreateCommand(ctx, "hash.hscan_expired", test_open_key_access_expired_hscan,"", 0, 0, 0) == REDISMODULE_ERR)
235 return REDISMODULE_ERR;
236 if (RedisModule_CreateCommand(ctx, "hash.hget_expire", test_open_key_hget_expire,"", 0, 0, 0) == REDISMODULE_ERR)
237 return REDISMODULE_ERR;
238 if (RedisModule_CreateCommand(ctx, "hash.hget_two_expire", test_open_key_hget_two_expire,"", 0, 0, 0) == REDISMODULE_ERR)
239 return REDISMODULE_ERR;
240 if (RedisModule_CreateCommand(ctx, "hash.hget_min_expire", test_open_key_hget_min_expire,"", 0, 0, 0) == REDISMODULE_ERR)
241 return REDISMODULE_ERR;
242
243 return REDISMODULE_OK;
244}
diff --git a/examples/redis-unstable/tests/modules/hooks.c b/examples/redis-unstable/tests/modules/hooks.c
new file mode 100644
index 0000000..59c8492
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/hooks.c
@@ -0,0 +1,496 @@
1/* This module is used to test the server events hooks API.
2 *
3 * -----------------------------------------------------------------------------
4 *
5 * Copyright (c) 2019-Present, Redis Ltd.
6 * All rights reserved.
7 *
8 * Licensed under your choice of (a) the Redis Source Available License 2.0
9 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
10 * GNU Affero General Public License v3 (AGPLv3).
11 */
12
13#include "redismodule.h"
14#include <stdio.h>
15#include <string.h>
16#include <strings.h>
17#include <assert.h>
18
19/* We need to store events to be able to test and see what we got, and we can't
20 * store them in the key-space since that would mess up rdb loading (duplicates)
21 * and be lost of flushdb. */
22RedisModuleDict *event_log = NULL;
23/* stores all the keys on which we got 'removed' event */
24RedisModuleDict *removed_event_log = NULL;
25/* stores all the subevent on which we got 'removed' event */
26RedisModuleDict *removed_subevent_type = NULL;
27/* stores all the keys on which we got 'removed' event with expiry information */
28RedisModuleDict *removed_expiry_log = NULL;
29
30typedef struct EventElement {
31 long count;
32 RedisModuleString *last_val_string;
33 long last_val_int;
34} EventElement;
35
36void LogStringEvent(RedisModuleCtx *ctx, const char* keyname, const char* data) {
37 EventElement *event = RedisModule_DictGetC(event_log, (void*)keyname, strlen(keyname), NULL);
38 if (!event) {
39 event = RedisModule_Alloc(sizeof(EventElement));
40 memset(event, 0, sizeof(EventElement));
41 RedisModule_DictSetC(event_log, (void*)keyname, strlen(keyname), event);
42 }
43 if (event->last_val_string) RedisModule_FreeString(ctx, event->last_val_string);
44 event->last_val_string = RedisModule_CreateString(ctx, data, strlen(data));
45 event->count++;
46}
47
48void LogNumericEvent(RedisModuleCtx *ctx, const char* keyname, long data) {
49 REDISMODULE_NOT_USED(ctx);
50 EventElement *event = RedisModule_DictGetC(event_log, (void*)keyname, strlen(keyname), NULL);
51 if (!event) {
52 event = RedisModule_Alloc(sizeof(EventElement));
53 memset(event, 0, sizeof(EventElement));
54 RedisModule_DictSetC(event_log, (void*)keyname, strlen(keyname), event);
55 }
56 event->last_val_int = data;
57 event->count++;
58}
59
60void FreeEvent(RedisModuleCtx *ctx, EventElement *event) {
61 if (event->last_val_string)
62 RedisModule_FreeString(ctx, event->last_val_string);
63 RedisModule_Free(event);
64}
65
66int cmdEventCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
67{
68 if (argc != 2){
69 RedisModule_WrongArity(ctx);
70 return REDISMODULE_OK;
71 }
72
73 EventElement *event = RedisModule_DictGet(event_log, argv[1], NULL);
74 RedisModule_ReplyWithLongLong(ctx, event? event->count: 0);
75 return REDISMODULE_OK;
76}
77
78int cmdEventLast(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
79{
80 if (argc != 2){
81 RedisModule_WrongArity(ctx);
82 return REDISMODULE_OK;
83 }
84
85 EventElement *event = RedisModule_DictGet(event_log, argv[1], NULL);
86 if (event && event->last_val_string)
87 RedisModule_ReplyWithString(ctx, event->last_val_string);
88 else if (event)
89 RedisModule_ReplyWithLongLong(ctx, event->last_val_int);
90 else
91 RedisModule_ReplyWithNull(ctx);
92 return REDISMODULE_OK;
93}
94
95void clearEvents(RedisModuleCtx *ctx)
96{
97 RedisModuleString *key;
98 EventElement *event;
99 RedisModuleDictIter *iter = RedisModule_DictIteratorStart(event_log, "^", NULL);
100 while((key = RedisModule_DictNext(ctx, iter, (void**)&event)) != NULL) {
101 event->count = 0;
102 event->last_val_int = 0;
103 if (event->last_val_string) RedisModule_FreeString(ctx, event->last_val_string);
104 event->last_val_string = NULL;
105 RedisModule_DictDel(event_log, key, NULL);
106 RedisModule_Free(event);
107 }
108 RedisModule_DictIteratorStop(iter);
109}
110
111int cmdEventsClear(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
112{
113 REDISMODULE_NOT_USED(argc);
114 REDISMODULE_NOT_USED(argv);
115 clearEvents(ctx);
116 return REDISMODULE_OK;
117}
118
119/* Client state change callback. */
120void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
121{
122 REDISMODULE_NOT_USED(e);
123
124 RedisModuleClientInfo *ci = data;
125 char *keyname = (sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ?
126 "client-connected" : "client-disconnected";
127 LogNumericEvent(ctx, keyname, ci->id);
128}
129
130void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
131{
132 REDISMODULE_NOT_USED(e);
133
134 RedisModuleFlushInfo *fi = data;
135 char *keyname = (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) ?
136 "flush-start" : "flush-end";
137 LogNumericEvent(ctx, keyname, fi->dbnum);
138}
139
140void roleChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
141{
142 REDISMODULE_NOT_USED(e);
143 REDISMODULE_NOT_USED(data);
144
145 RedisModuleReplicationInfo *ri = data;
146 char *keyname = (sub == REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER) ?
147 "role-master" : "role-replica";
148 LogStringEvent(ctx, keyname, ri->masterhost);
149}
150
151void replicationChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
152{
153 REDISMODULE_NOT_USED(e);
154 REDISMODULE_NOT_USED(data);
155
156 char *keyname = (sub == REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE) ?
157 "replica-online" : "replica-offline";
158 LogNumericEvent(ctx, keyname, 0);
159}
160
161void rasterLinkChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
162{
163 REDISMODULE_NOT_USED(e);
164 REDISMODULE_NOT_USED(data);
165
166 char *keyname = (sub == REDISMODULE_SUBEVENT_MASTER_LINK_UP) ?
167 "masterlink-up" : "masterlink-down";
168 LogNumericEvent(ctx, keyname, 0);
169}
170
171void persistenceCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
172{
173 REDISMODULE_NOT_USED(e);
174 REDISMODULE_NOT_USED(data);
175
176 char *keyname = NULL;
177 switch (sub) {
178 case REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START: keyname = "persistence-rdb-start"; break;
179 case REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START: keyname = "persistence-aof-start"; break;
180 case REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_AOF_START: keyname = "persistence-syncaof-start"; break;
181 case REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START: keyname = "persistence-syncrdb-start"; break;
182 case REDISMODULE_SUBEVENT_PERSISTENCE_ENDED: keyname = "persistence-end"; break;
183 case REDISMODULE_SUBEVENT_PERSISTENCE_FAILED: keyname = "persistence-failed"; break;
184 }
185 /* modifying the keyspace from the fork child is not an option, using log instead */
186 RedisModule_Log(ctx, "warning", "module-event-%s", keyname);
187 if (sub == REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START ||
188 sub == REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_AOF_START)
189 {
190 LogNumericEvent(ctx, keyname, 0);
191 }
192}
193
194void loadingCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
195{
196 REDISMODULE_NOT_USED(e);
197 REDISMODULE_NOT_USED(data);
198
199 char *keyname = NULL;
200 switch (sub) {
201 case REDISMODULE_SUBEVENT_LOADING_RDB_START: keyname = "loading-rdb-start"; break;
202 case REDISMODULE_SUBEVENT_LOADING_AOF_START: keyname = "loading-aof-start"; break;
203 case REDISMODULE_SUBEVENT_LOADING_REPL_START: keyname = "loading-repl-start"; break;
204 case REDISMODULE_SUBEVENT_LOADING_ENDED: keyname = "loading-end"; break;
205 case REDISMODULE_SUBEVENT_LOADING_FAILED: keyname = "loading-failed"; break;
206 }
207 LogNumericEvent(ctx, keyname, 0);
208}
209
210void loadingProgressCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
211{
212 REDISMODULE_NOT_USED(e);
213
214 RedisModuleLoadingProgress *ei = data;
215 char *keyname = (sub == REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB) ?
216 "loading-progress-rdb" : "loading-progress-aof";
217 LogNumericEvent(ctx, keyname, ei->progress);
218}
219
220void shutdownCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
221{
222 REDISMODULE_NOT_USED(e);
223 REDISMODULE_NOT_USED(data);
224 REDISMODULE_NOT_USED(sub);
225
226 RedisModule_Log(ctx, "warning", "module-event-%s", "shutdown");
227}
228
229void cronLoopCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
230{
231 REDISMODULE_NOT_USED(e);
232 REDISMODULE_NOT_USED(sub);
233
234 RedisModuleCronLoop *ei = data;
235 LogNumericEvent(ctx, "cron-loop", ei->hz);
236}
237
238void moduleChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
239{
240 REDISMODULE_NOT_USED(e);
241
242 RedisModuleModuleChange *ei = data;
243 char *keyname = (sub == REDISMODULE_SUBEVENT_MODULE_LOADED) ?
244 "module-loaded" : "module-unloaded";
245 LogStringEvent(ctx, keyname, ei->module_name);
246}
247
248void swapDbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
249{
250 REDISMODULE_NOT_USED(e);
251 REDISMODULE_NOT_USED(sub);
252
253 RedisModuleSwapDbInfo *ei = data;
254 LogNumericEvent(ctx, "swapdb-first", ei->dbnum_first);
255 LogNumericEvent(ctx, "swapdb-second", ei->dbnum_second);
256}
257
258void configChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
259{
260 REDISMODULE_NOT_USED(e);
261 if (sub != REDISMODULE_SUBEVENT_CONFIG_CHANGE) {
262 return;
263 }
264
265 RedisModuleConfigChangeV1 *ei = data;
266 LogNumericEvent(ctx, "config-change-count", ei->num_changes);
267 LogStringEvent(ctx, "config-change-first", ei->config_names[0]);
268}
269
270void keyInfoCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
271{
272 REDISMODULE_NOT_USED(e);
273
274 RedisModuleKeyInfoV1 *ei = data;
275 RedisModuleKey *kp = ei->key;
276 RedisModuleString *key = (RedisModuleString *) RedisModule_GetKeyNameFromModuleKey(kp);
277 const char *keyname = RedisModule_StringPtrLen(key, NULL);
278 RedisModuleString *event_keyname = RedisModule_CreateStringPrintf(ctx, "key-info-%s", keyname);
279 LogStringEvent(ctx, RedisModule_StringPtrLen(event_keyname, NULL), keyname);
280 RedisModule_FreeString(ctx, event_keyname);
281
282 /* Despite getting a key object from the callback, we also try to re-open it
283 * to make sure the callback is called before it is actually removed from the keyspace. */
284 RedisModuleKey *kp_open = RedisModule_OpenKey(ctx, key, REDISMODULE_READ);
285 assert(RedisModule_ValueLength(kp) == RedisModule_ValueLength(kp_open));
286 RedisModule_CloseKey(kp_open);
287
288 /* We also try to RM_Call a command that accesses that key, also to make sure it's still in the keyspace. */
289 char *size_command = NULL;
290 int key_type = RedisModule_KeyType(kp);
291 if (key_type == REDISMODULE_KEYTYPE_STRING) {
292 size_command = "STRLEN";
293 } else if (key_type == REDISMODULE_KEYTYPE_LIST) {
294 size_command = "LLEN";
295 } else if (key_type == REDISMODULE_KEYTYPE_HASH) {
296 size_command = "HLEN";
297 } else if (key_type == REDISMODULE_KEYTYPE_SET) {
298 size_command = "SCARD";
299 } else if (key_type == REDISMODULE_KEYTYPE_ZSET) {
300 size_command = "ZCARD";
301 } else if (key_type == REDISMODULE_KEYTYPE_STREAM) {
302 size_command = "XLEN";
303 }
304 if (size_command != NULL) {
305 RedisModuleCallReply *reply = RedisModule_Call(ctx, size_command, "s", key);
306 assert(reply != NULL);
307 assert(RedisModule_ValueLength(kp) == (size_t) RedisModule_CallReplyInteger(reply));
308 RedisModule_FreeCallReply(reply);
309 }
310
311 /* Now use the key object we got from the callback for various validations. */
312 RedisModuleString *prev = RedisModule_DictGetC(removed_event_log, (void*)keyname, strlen(keyname), NULL);
313 /* We keep object length */
314 RedisModuleString *v = RedisModule_CreateStringPrintf(ctx, "%zd", RedisModule_ValueLength(kp));
315 /* For string type, we keep value instead of length */
316 if (RedisModule_KeyType(kp) == REDISMODULE_KEYTYPE_STRING) {
317 RedisModule_FreeString(ctx, v);
318 size_t len;
319 /* We need to access the string value with RedisModule_StringDMA.
320 * RedisModule_StringDMA may call dbUnshareStringValue to free the origin object,
321 * so we also can test it. */
322 char *s = RedisModule_StringDMA(kp, &len, REDISMODULE_READ);
323 v = RedisModule_CreateString(ctx, s, len);
324 }
325 RedisModule_DictReplaceC(removed_event_log, (void*)keyname, strlen(keyname), v);
326 if (prev != NULL) {
327 RedisModule_FreeString(ctx, prev);
328 }
329
330 const char *subevent = "deleted";
331 if (sub == REDISMODULE_SUBEVENT_KEY_EXPIRED) {
332 subevent = "expired";
333 } else if (sub == REDISMODULE_SUBEVENT_KEY_EVICTED) {
334 subevent = "evicted";
335 } else if (sub == REDISMODULE_SUBEVENT_KEY_OVERWRITTEN) {
336 subevent = "overwritten";
337 }
338 RedisModule_DictReplaceC(removed_subevent_type, (void*)keyname, strlen(keyname), (void *)subevent);
339
340 RedisModuleString *prevexpire = RedisModule_DictGetC(removed_expiry_log, (void*)keyname, strlen(keyname), NULL);
341 RedisModuleString *expire = RedisModule_CreateStringPrintf(ctx, "%lld", RedisModule_GetAbsExpire(kp));
342 RedisModule_DictReplaceC(removed_expiry_log, (void*)keyname, strlen(keyname), (void *)expire);
343 if (prevexpire != NULL) {
344 RedisModule_FreeString(ctx, prevexpire);
345 }
346}
347
348static int cmdIsKeyRemoved(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
349 if(argc != 2){
350 return RedisModule_WrongArity(ctx);
351 }
352
353 const char *key = RedisModule_StringPtrLen(argv[1], NULL);
354
355 RedisModuleString *value = RedisModule_DictGetC(removed_event_log, (void*)key, strlen(key), NULL);
356
357 if (value == NULL) {
358 return RedisModule_ReplyWithError(ctx, "ERR Key was not removed");
359 }
360
361 const char *subevent = RedisModule_DictGetC(removed_subevent_type, (void*)key, strlen(key), NULL);
362 RedisModule_ReplyWithArray(ctx, 2);
363 RedisModule_ReplyWithString(ctx, value);
364 RedisModule_ReplyWithSimpleString(ctx, subevent);
365
366 return REDISMODULE_OK;
367}
368
369static int cmdKeyExpiry(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
370 if(argc != 2){
371 return RedisModule_WrongArity(ctx);
372 }
373
374 const char* key = RedisModule_StringPtrLen(argv[1], NULL);
375 RedisModuleString *expire = RedisModule_DictGetC(removed_expiry_log, (void*)key, strlen(key), NULL);
376 if (expire == NULL) {
377 return RedisModule_ReplyWithError(ctx, "ERR Key was not removed");
378 }
379 RedisModule_ReplyWithString(ctx, expire);
380 return REDISMODULE_OK;
381}
382
383/* This function must be present on each Redis module. It is used in order to
384 * register the commands into the Redis server. */
385int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
386#define VerifySubEventSupported(e, s) \
387 if (!RedisModule_IsSubEventSupported(e, s)) { \
388 return REDISMODULE_ERR; \
389 }
390
391 if (RedisModule_Init(ctx,"testhook",1,REDISMODULE_APIVER_1)
392 == REDISMODULE_ERR) return REDISMODULE_ERR;
393
394 /* Example on how to check if a server sub event is supported */
395 if (!RedisModule_IsSubEventSupported(RedisModuleEvent_ReplicationRoleChanged, REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER)) {
396 return REDISMODULE_ERR;
397 }
398
399 /* replication related hooks */
400 RedisModule_SubscribeToServerEvent(ctx,
401 RedisModuleEvent_ReplicationRoleChanged, roleChangeCallback);
402 RedisModule_SubscribeToServerEvent(ctx,
403 RedisModuleEvent_ReplicaChange, replicationChangeCallback);
404 RedisModule_SubscribeToServerEvent(ctx,
405 RedisModuleEvent_MasterLinkChange, rasterLinkChangeCallback);
406
407 /* persistence related hooks */
408 RedisModule_SubscribeToServerEvent(ctx,
409 RedisModuleEvent_Persistence, persistenceCallback);
410 RedisModule_SubscribeToServerEvent(ctx,
411 RedisModuleEvent_Loading, loadingCallback);
412 RedisModule_SubscribeToServerEvent(ctx,
413 RedisModuleEvent_LoadingProgress, loadingProgressCallback);
414
415 /* other hooks */
416 RedisModule_SubscribeToServerEvent(ctx,
417 RedisModuleEvent_ClientChange, clientChangeCallback);
418 RedisModule_SubscribeToServerEvent(ctx,
419 RedisModuleEvent_FlushDB, flushdbCallback);
420 RedisModule_SubscribeToServerEvent(ctx,
421 RedisModuleEvent_Shutdown, shutdownCallback);
422 RedisModule_SubscribeToServerEvent(ctx,
423 RedisModuleEvent_CronLoop, cronLoopCallback);
424
425 RedisModule_SubscribeToServerEvent(ctx,
426 RedisModuleEvent_ModuleChange, moduleChangeCallback);
427 RedisModule_SubscribeToServerEvent(ctx,
428 RedisModuleEvent_SwapDB, swapDbCallback);
429
430 RedisModule_SubscribeToServerEvent(ctx,
431 RedisModuleEvent_Config, configChangeCallback);
432
433 RedisModule_SubscribeToServerEvent(ctx,
434 RedisModuleEvent_Key, keyInfoCallback);
435
436 event_log = RedisModule_CreateDict(ctx);
437 removed_event_log = RedisModule_CreateDict(ctx);
438 removed_subevent_type = RedisModule_CreateDict(ctx);
439 removed_expiry_log = RedisModule_CreateDict(ctx);
440
441 if (RedisModule_CreateCommand(ctx,"hooks.event_count", cmdEventCount,"",0,0,0) == REDISMODULE_ERR)
442 return REDISMODULE_ERR;
443 if (RedisModule_CreateCommand(ctx,"hooks.event_last", cmdEventLast,"",0,0,0) == REDISMODULE_ERR)
444 return REDISMODULE_ERR;
445 if (RedisModule_CreateCommand(ctx,"hooks.clear", cmdEventsClear,"",0,0,0) == REDISMODULE_ERR)
446 return REDISMODULE_ERR;
447 if (RedisModule_CreateCommand(ctx,"hooks.is_key_removed", cmdIsKeyRemoved,"",0,0,0) == REDISMODULE_ERR)
448 return REDISMODULE_ERR;
449 if (RedisModule_CreateCommand(ctx,"hooks.pexpireat", cmdKeyExpiry,"",0,0,0) == REDISMODULE_ERR)
450 return REDISMODULE_ERR;
451
452 if (argc == 1) {
453 const char *ptr = RedisModule_StringPtrLen(argv[0], NULL);
454 if (!strcasecmp(ptr, "noload")) {
455 /* This is a hint that we return ERR at the last moment of OnLoad. */
456 RedisModule_FreeDict(ctx, event_log);
457 RedisModule_FreeDict(ctx, removed_event_log);
458 RedisModule_FreeDict(ctx, removed_subevent_type);
459 RedisModule_FreeDict(ctx, removed_expiry_log);
460 return REDISMODULE_ERR;
461 }
462 }
463
464 return REDISMODULE_OK;
465}
466
467int RedisModule_OnUnload(RedisModuleCtx *ctx) {
468 clearEvents(ctx);
469 RedisModule_FreeDict(ctx, event_log);
470 event_log = NULL;
471
472 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(removed_event_log, "^", NULL, 0);
473 char* key;
474 size_t keyLen;
475 RedisModuleString* val;
476 while((key = RedisModule_DictNextC(iter, &keyLen, (void**)&val))){
477 RedisModule_FreeString(ctx, val);
478 }
479 RedisModule_FreeDict(ctx, removed_event_log);
480 RedisModule_DictIteratorStop(iter);
481 removed_event_log = NULL;
482
483 RedisModule_FreeDict(ctx, removed_subevent_type);
484 removed_subevent_type = NULL;
485
486 iter = RedisModule_DictIteratorStartC(removed_expiry_log, "^", NULL, 0);
487 while((key = RedisModule_DictNextC(iter, &keyLen, (void**)&val))){
488 RedisModule_FreeString(ctx, val);
489 }
490 RedisModule_FreeDict(ctx, removed_expiry_log);
491 RedisModule_DictIteratorStop(iter);
492 removed_expiry_log = NULL;
493
494 return REDISMODULE_OK;
495}
496
diff --git a/examples/redis-unstable/tests/modules/infotest.c b/examples/redis-unstable/tests/modules/infotest.c
new file mode 100644
index 0000000..b93a0c4
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/infotest.c
@@ -0,0 +1,119 @@
1#include "redismodule.h"
2
3#include <string.h>
4
5void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
6 RedisModule_InfoAddSection(ctx, "");
7 RedisModule_InfoAddFieldLongLong(ctx, "global", -2);
8 RedisModule_InfoAddFieldULongLong(ctx, "uglobal", (unsigned long long)-2);
9
10 RedisModule_InfoAddSection(ctx, "Spanish");
11 RedisModule_InfoAddFieldCString(ctx, "uno", "one");
12 RedisModule_InfoAddFieldLongLong(ctx, "dos", 2);
13
14 RedisModule_InfoAddSection(ctx, "Italian");
15 RedisModule_InfoAddFieldLongLong(ctx, "due", 2);
16 RedisModule_InfoAddFieldDouble(ctx, "tre", 3.3);
17
18 RedisModule_InfoAddSection(ctx, "keyspace");
19 RedisModule_InfoBeginDictField(ctx, "db0");
20 RedisModule_InfoAddFieldLongLong(ctx, "keys", 3);
21 RedisModule_InfoAddFieldLongLong(ctx, "expires", 1);
22 RedisModule_InfoEndDictField(ctx);
23
24 RedisModule_InfoAddSection(ctx, "unsafe");
25 RedisModule_InfoBeginDictField(ctx, "unsafe:field");
26 RedisModule_InfoAddFieldLongLong(ctx, "value", 1);
27 RedisModule_InfoEndDictField(ctx);
28
29 if (for_crash_report) {
30 RedisModule_InfoAddSection(ctx, "Klingon");
31 RedisModule_InfoAddFieldCString(ctx, "one", "wa'");
32 RedisModule_InfoAddFieldCString(ctx, "two", "cha'");
33 RedisModule_InfoAddFieldCString(ctx, "three", "wej");
34 }
35
36}
37
38int info_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, char field_type)
39{
40 if (argc != 3 && argc != 4) {
41 RedisModule_WrongArity(ctx);
42 return REDISMODULE_OK;
43 }
44 int err = REDISMODULE_OK;
45 const char *section, *field;
46 section = RedisModule_StringPtrLen(argv[1], NULL);
47 field = RedisModule_StringPtrLen(argv[2], NULL);
48 RedisModuleServerInfoData *info = RedisModule_GetServerInfo(ctx, section);
49 if (field_type=='i') {
50 long long ll = RedisModule_ServerInfoGetFieldSigned(info, field, &err);
51 if (err==REDISMODULE_OK)
52 RedisModule_ReplyWithLongLong(ctx, ll);
53 } else if (field_type=='u') {
54 unsigned long long ll = (unsigned long long)RedisModule_ServerInfoGetFieldUnsigned(info, field, &err);
55 if (err==REDISMODULE_OK)
56 RedisModule_ReplyWithLongLong(ctx, ll);
57 } else if (field_type=='d') {
58 double d = RedisModule_ServerInfoGetFieldDouble(info, field, &err);
59 if (err==REDISMODULE_OK)
60 RedisModule_ReplyWithDouble(ctx, d);
61 } else if (field_type=='c') {
62 const char *str = RedisModule_ServerInfoGetFieldC(info, field);
63 if (str)
64 RedisModule_ReplyWithCString(ctx, str);
65 } else {
66 RedisModuleString *str = RedisModule_ServerInfoGetField(ctx, info, field);
67 if (str) {
68 RedisModule_ReplyWithString(ctx, str);
69 RedisModule_FreeString(ctx, str);
70 } else
71 err=REDISMODULE_ERR;
72 }
73 if (err!=REDISMODULE_OK)
74 RedisModule_ReplyWithError(ctx, "not found");
75 RedisModule_FreeServerInfo(ctx, info);
76 return REDISMODULE_OK;
77}
78
79int info_gets(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
80 return info_get(ctx, argv, argc, 's');
81}
82
83int info_getc(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
84 return info_get(ctx, argv, argc, 'c');
85}
86
87int info_geti(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
88 return info_get(ctx, argv, argc, 'i');
89}
90
91int info_getu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
92 return info_get(ctx, argv, argc, 'u');
93}
94
95int info_getd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
96 return info_get(ctx, argv, argc, 'd');
97}
98
99int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
100 REDISMODULE_NOT_USED(argv);
101 REDISMODULE_NOT_USED(argc);
102 if (RedisModule_Init(ctx,"infotest",1,REDISMODULE_APIVER_1)
103 == REDISMODULE_ERR) return REDISMODULE_ERR;
104
105 if (RedisModule_RegisterInfoFunc(ctx, InfoFunc) == REDISMODULE_ERR) return REDISMODULE_ERR;
106
107 if (RedisModule_CreateCommand(ctx,"info.gets", info_gets,"",0,0,0) == REDISMODULE_ERR)
108 return REDISMODULE_ERR;
109 if (RedisModule_CreateCommand(ctx,"info.getc", info_getc,"",0,0,0) == REDISMODULE_ERR)
110 return REDISMODULE_ERR;
111 if (RedisModule_CreateCommand(ctx,"info.geti", info_geti,"",0,0,0) == REDISMODULE_ERR)
112 return REDISMODULE_ERR;
113 if (RedisModule_CreateCommand(ctx,"info.getu", info_getu,"",0,0,0) == REDISMODULE_ERR)
114 return REDISMODULE_ERR;
115 if (RedisModule_CreateCommand(ctx,"info.getd", info_getd,"",0,0,0) == REDISMODULE_ERR)
116 return REDISMODULE_ERR;
117
118 return REDISMODULE_OK;
119}
diff --git a/examples/redis-unstable/tests/modules/internalsecret.c b/examples/redis-unstable/tests/modules/internalsecret.c
new file mode 100644
index 0000000..043089c
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/internalsecret.c
@@ -0,0 +1,154 @@
1#include "redismodule.h"
2#include <errno.h>
3
4int InternalAuth_GetInternalSecret(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
5 REDISMODULE_NOT_USED(argv);
6 REDISMODULE_NOT_USED(argc);
7
8 /* NOTE: The internal secret SHOULD NOT be exposed by any module. This is
9 done for testing purposes only. */
10 size_t len;
11 const char *secret = RedisModule_GetInternalSecret(ctx, &len);
12 if(secret) {
13 RedisModule_ReplyWithStringBuffer(ctx, secret, len);
14 } else {
15 RedisModule_ReplyWithError(ctx, "ERR no internal secret available");
16 }
17 return REDISMODULE_OK;
18}
19
20int InternalAuth_InternalCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
21 REDISMODULE_NOT_USED(argv);
22 REDISMODULE_NOT_USED(argc);
23
24 RedisModule_ReplyWithSimpleString(ctx, "OK");
25 return REDISMODULE_OK;
26}
27
28typedef enum {
29 RM_CALL_REGULAR = 0,
30 RM_CALL_WITHUSER = 1,
31 RM_CALL_WITHDETACHEDCLIENT = 2,
32 RM_CALL_REPLICATED = 3
33} RMCallMode;
34
35int call_rm_call(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, RMCallMode mode) {
36 if(argc < 2){
37 return RedisModule_WrongArity(ctx);
38 }
39 RedisModuleCallReply *rep = NULL;
40 RedisModuleCtx *detached_ctx = NULL;
41 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
42
43 switch (mode) {
44 case RM_CALL_REGULAR:
45 // Regular call, with the unrestricted user.
46 rep = RedisModule_Call(ctx, cmd, "vE", argv + 2, (size_t)argc - 2);
47 break;
48 case RM_CALL_WITHUSER:
49 // Simply call the command with the current client.
50 rep = RedisModule_Call(ctx, cmd, "vCE", argv + 2, (size_t)argc - 2);
51 break;
52 case RM_CALL_WITHDETACHEDCLIENT:
53 // Use a context created with the thread-safe-context API
54 detached_ctx = RedisModule_GetThreadSafeContext(NULL);
55 if(!detached_ctx){
56 RedisModule_ReplyWithError(ctx, "ERR failed to create detached context");
57 return REDISMODULE_ERR;
58 }
59 // Dispatch the command with the detached context
60 rep = RedisModule_Call(detached_ctx, cmd, "vCE", argv + 2, (size_t)argc - 2);
61 break;
62 case RM_CALL_REPLICATED:
63 rep = RedisModule_Call(ctx, cmd, "vE", argv + 2, (size_t)argc - 2);
64 }
65
66 if(!rep) {
67 char err[100];
68 switch (errno) {
69 case EACCES:
70 RedisModule_ReplyWithError(ctx, "ERR NOPERM");
71 break;
72 case ENOENT:
73 RedisModule_ReplyWithError(ctx, "ERR unknown command");
74 break;
75 default:
76 snprintf(err, sizeof(err) - 1, "ERR errno=%d", errno);
77 RedisModule_ReplyWithError(ctx, err);
78 break;
79 }
80 } else {
81 RedisModule_ReplyWithCallReply(ctx, rep);
82 RedisModule_FreeCallReply(rep);
83 if (mode == RM_CALL_REPLICATED)
84 RedisModule_ReplicateVerbatim(ctx);
85 }
86
87 if (mode == RM_CALL_WITHDETACHEDCLIENT) {
88 RedisModule_FreeThreadSafeContext(detached_ctx);
89 }
90
91 return REDISMODULE_OK;
92}
93
94int internal_rmcall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
95 return call_rm_call(ctx, argv, argc, RM_CALL_REGULAR);
96}
97
98int noninternal_rmcall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
99 return call_rm_call(ctx, argv, argc, RM_CALL_REGULAR);
100}
101
102int noninternal_rmcall_withuser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
103 return call_rm_call(ctx, argv, argc, RM_CALL_WITHUSER);
104}
105
106int noninternal_rmcall_detachedcontext_withuser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
107 return call_rm_call(ctx, argv, argc, RM_CALL_WITHDETACHEDCLIENT);
108}
109
110int internal_rmcall_replicated(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
111 return call_rm_call(ctx, argv, argc, RM_CALL_REPLICATED);
112}
113
114/* This function must be present on each Redis module. It is used in order to
115 * register the commands into the Redis server. */
116int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
117 REDISMODULE_NOT_USED(argv);
118 REDISMODULE_NOT_USED(argc);
119
120 if (RedisModule_Init(ctx,"testinternalsecret",1,REDISMODULE_APIVER_1)
121 == REDISMODULE_ERR) return REDISMODULE_ERR;
122
123 /* WARNING: A module should NEVER expose the internal secret - this is for
124 * testing purposes only. */
125 if (RedisModule_CreateCommand(ctx,"internalauth.getinternalsecret",
126 InternalAuth_GetInternalSecret,"",0,0,0) == REDISMODULE_ERR)
127 return REDISMODULE_ERR;
128
129 if (RedisModule_CreateCommand(ctx,"internalauth.internalcommand",
130 InternalAuth_InternalCommand,"internal",0,0,0) == REDISMODULE_ERR)
131 return REDISMODULE_ERR;
132
133 if (RedisModule_CreateCommand(ctx,"internalauth.internal_rmcall",
134 internal_rmcall,"write internal",0,0,0) == REDISMODULE_ERR)
135 return REDISMODULE_ERR;
136
137 if (RedisModule_CreateCommand(ctx,"internalauth.noninternal_rmcall",
138 noninternal_rmcall,"write",0,0,0) == REDISMODULE_ERR)
139 return REDISMODULE_ERR;
140
141 if (RedisModule_CreateCommand(ctx,"internalauth.noninternal_rmcall_withuser",
142 noninternal_rmcall_withuser,"write",0,0,0) == REDISMODULE_ERR)
143 return REDISMODULE_ERR;
144
145 if (RedisModule_CreateCommand(ctx,"internalauth.noninternal_rmcall_detachedcontext_withuser",
146 noninternal_rmcall_detachedcontext_withuser,"write",0,0,0) == REDISMODULE_ERR)
147 return REDISMODULE_ERR;
148
149 if (RedisModule_CreateCommand(ctx,"internalauth.internal_rmcall_replicated",
150 internal_rmcall_replicated,"write internal",0,0,0) == REDISMODULE_ERR)
151 return REDISMODULE_ERR;
152
153 return REDISMODULE_OK;
154}
diff --git a/examples/redis-unstable/tests/modules/keyspace_events.c b/examples/redis-unstable/tests/modules/keyspace_events.c
new file mode 100644
index 0000000..146261f
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/keyspace_events.c
@@ -0,0 +1,479 @@
1/* This module is used to test the server keyspace events API.
2 *
3 * -----------------------------------------------------------------------------
4 *
5 * Copyright (c) 2020-Present, Redis Ltd.
6 * All rights reserved.
7 *
8 * Licensed under your choice of (a) the Redis Source Available License 2.0
9 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
10 * GNU Affero General Public License v3 (AGPLv3).
11 */
12
13#define _BSD_SOURCE
14#define _DEFAULT_SOURCE /* For usleep */
15
16#include "redismodule.h"
17#include <stdio.h>
18#include <string.h>
19#include <strings.h>
20#include <unistd.h>
21
22ustime_t cached_time = 0;
23
24/** stores all the keys on which we got 'loaded' keyspace notification **/
25RedisModuleDict *loaded_event_log = NULL;
26/** stores all the keys on which we got 'module' keyspace notification **/
27RedisModuleDict *module_event_log = NULL;
28
29/** Counts how many deleted KSN we got on keys with a prefix of "count_dels_" **/
30static size_t dels = 0;
31
32static int KeySpace_NotificationLoaded(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
33 REDISMODULE_NOT_USED(ctx);
34 REDISMODULE_NOT_USED(type);
35
36 if(strcmp(event, "loaded") == 0){
37 const char* keyName = RedisModule_StringPtrLen(key, NULL);
38 int nokey;
39 RedisModule_DictGetC(loaded_event_log, (void*)keyName, strlen(keyName), &nokey);
40 if(nokey){
41 RedisModule_DictSetC(loaded_event_log, (void*)keyName, strlen(keyName), RedisModule_HoldString(ctx, key));
42 }
43 }
44
45 return REDISMODULE_OK;
46}
47
48static long long callback_call_count = 0;
49static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
50 REDISMODULE_NOT_USED(type);
51 callback_call_count++;
52 const char *key_str = RedisModule_StringPtrLen(key, NULL);
53 if (strncmp(key_str, "count_dels_", 11) == 0 && strcmp(event, "del") == 0) {
54 if (RedisModule_GetContextFlags(ctx) & REDISMODULE_CTX_FLAGS_MASTER) {
55 dels++;
56 RedisModule_Replicate(ctx, "keyspace.incr_dels", "");
57 }
58 return REDISMODULE_OK;
59 }
60 if (cached_time) {
61 RedisModule_Assert(cached_time == RedisModule_CachedMicroseconds());
62 usleep(1);
63 RedisModule_Assert(cached_time != RedisModule_Microseconds());
64 }
65
66 if (strcmp(event, "del") == 0) {
67 RedisModuleString *copykey = RedisModule_CreateStringPrintf(ctx, "%s_copy", RedisModule_StringPtrLen(key, NULL));
68 RedisModuleCallReply* rep = RedisModule_Call(ctx, "DEL", "s!", copykey);
69 RedisModule_FreeString(ctx, copykey);
70 RedisModule_FreeCallReply(rep);
71
72 int ctx_flags = RedisModule_GetContextFlags(ctx);
73 if (ctx_flags & REDISMODULE_CTX_FLAGS_LUA) {
74 RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "c", "lua");
75 RedisModule_FreeCallReply(rep);
76 }
77 if (ctx_flags & REDISMODULE_CTX_FLAGS_MULTI) {
78 RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "c", "multi");
79 RedisModule_FreeCallReply(rep);
80 }
81 }
82
83 return REDISMODULE_OK;
84}
85
86static int KeySpace_NotificationExpired(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
87 REDISMODULE_NOT_USED(type);
88 REDISMODULE_NOT_USED(event);
89 REDISMODULE_NOT_USED(key);
90
91 RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "c!", "testkeyspace:expired");
92 RedisModule_FreeCallReply(rep);
93
94 return REDISMODULE_OK;
95}
96
97/* This key miss notification handler is performing a write command inside the notification callback.
98 * Notice, it is discourage and currently wrong to perform a write command inside key miss event.
99 * It can cause read commands to be replicated to the replica/aof. This test is here temporary (for coverage and
100 * verification that it's not crashing). */
101static int KeySpace_NotificationModuleKeyMiss(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
102 REDISMODULE_NOT_USED(type);
103 REDISMODULE_NOT_USED(event);
104 REDISMODULE_NOT_USED(key);
105
106 int flags = RedisModule_GetContextFlags(ctx);
107 if (!(flags & REDISMODULE_CTX_FLAGS_MASTER)) {
108 return REDISMODULE_OK; // ignore the event on replica
109 }
110
111 RedisModuleCallReply* rep = RedisModule_Call(ctx, "incr", "!c", "missed");
112 RedisModule_FreeCallReply(rep);
113
114 return REDISMODULE_OK;
115}
116
117static int KeySpace_NotificationModuleString(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
118 REDISMODULE_NOT_USED(type);
119 REDISMODULE_NOT_USED(event);
120 RedisModuleKey *redis_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ);
121
122 size_t len = 0;
123 /* RedisModule_StringDMA could change the data format and cause the old robj to be freed.
124 * This code verifies that such format change will not cause any crashes.*/
125 char *data = RedisModule_StringDMA(redis_key, &len, REDISMODULE_READ);
126 int res = strncmp(data, "dummy", 5);
127 REDISMODULE_NOT_USED(res);
128
129 RedisModule_CloseKey(redis_key);
130
131 return REDISMODULE_OK;
132}
133
134static void KeySpace_PostNotificationStringFreePD(void *pd) {
135 RedisModule_FreeString(NULL, pd);
136}
137
138static void KeySpace_PostNotificationString(RedisModuleCtx *ctx, void *pd) {
139 REDISMODULE_NOT_USED(ctx);
140 RedisModuleCallReply* rep = RedisModule_Call(ctx, "incr", "!s", pd);
141 RedisModule_FreeCallReply(rep);
142}
143
144static int KeySpace_NotificationModuleStringPostNotificationJob(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
145 REDISMODULE_NOT_USED(ctx);
146 REDISMODULE_NOT_USED(type);
147 REDISMODULE_NOT_USED(event);
148
149 const char *key_str = RedisModule_StringPtrLen(key, NULL);
150
151 if (strncmp(key_str, "string1_", 8) != 0) {
152 return REDISMODULE_OK;
153 }
154
155 RedisModuleString *new_key = RedisModule_CreateStringPrintf(NULL, "string_changed{%s}", key_str);
156 RedisModule_AddPostNotificationJob(ctx, KeySpace_PostNotificationString, new_key, KeySpace_PostNotificationStringFreePD);
157 return REDISMODULE_OK;
158}
159
160static int KeySpace_NotificationModule(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
161 REDISMODULE_NOT_USED(ctx);
162 REDISMODULE_NOT_USED(type);
163 REDISMODULE_NOT_USED(event);
164
165 const char* keyName = RedisModule_StringPtrLen(key, NULL);
166 int nokey;
167 RedisModule_DictGetC(module_event_log, (void*)keyName, strlen(keyName), &nokey);
168 if(nokey){
169 RedisModule_DictSetC(module_event_log, (void*)keyName, strlen(keyName), RedisModule_HoldString(ctx, key));
170 }
171 return REDISMODULE_OK;
172}
173
174static int cmdNotify(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
175 if(argc != 2){
176 return RedisModule_WrongArity(ctx);
177 }
178
179 RedisModule_NotifyKeyspaceEvent(ctx, REDISMODULE_NOTIFY_MODULE, "notify", argv[1]);
180 RedisModule_ReplyWithNull(ctx);
181 return REDISMODULE_OK;
182}
183
184static int cmdIsModuleKeyNotified(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
185 if(argc != 2){
186 return RedisModule_WrongArity(ctx);
187 }
188
189 const char* key = RedisModule_StringPtrLen(argv[1], NULL);
190
191 int nokey;
192 RedisModuleString* keyStr = RedisModule_DictGetC(module_event_log, (void*)key, strlen(key), &nokey);
193
194 RedisModule_ReplyWithArray(ctx, 2);
195 RedisModule_ReplyWithLongLong(ctx, !nokey);
196 if(nokey){
197 RedisModule_ReplyWithNull(ctx);
198 }else{
199 RedisModule_ReplyWithString(ctx, keyStr);
200 }
201 return REDISMODULE_OK;
202}
203
204static int cmdIsKeyLoaded(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
205 if(argc != 2){
206 return RedisModule_WrongArity(ctx);
207 }
208
209 const char* key = RedisModule_StringPtrLen(argv[1], NULL);
210
211 int nokey;
212 RedisModuleString* keyStr = RedisModule_DictGetC(loaded_event_log, (void*)key, strlen(key), &nokey);
213
214 RedisModule_ReplyWithArray(ctx, 2);
215 RedisModule_ReplyWithLongLong(ctx, !nokey);
216 if(nokey){
217 RedisModule_ReplyWithNull(ctx);
218 }else{
219 RedisModule_ReplyWithString(ctx, keyStr);
220 }
221 return REDISMODULE_OK;
222}
223
224static int cmdDelKeyCopy(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
225 if (argc != 2)
226 return RedisModule_WrongArity(ctx);
227
228 cached_time = RedisModule_CachedMicroseconds();
229
230 RedisModuleCallReply* rep = RedisModule_Call(ctx, "DEL", "s!", argv[1]);
231 if (!rep) {
232 RedisModule_ReplyWithError(ctx, "NULL reply returned");
233 } else {
234 RedisModule_ReplyWithCallReply(ctx, rep);
235 RedisModule_FreeCallReply(rep);
236 }
237 cached_time = 0;
238 return REDISMODULE_OK;
239}
240
241/* Call INCR and propagate using RM_Call with `!`. */
242static int cmdIncrCase1(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
243 if (argc != 2)
244 return RedisModule_WrongArity(ctx);
245
246 RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "s!", argv[1]);
247 if (!rep) {
248 RedisModule_ReplyWithError(ctx, "NULL reply returned");
249 } else {
250 RedisModule_ReplyWithCallReply(ctx, rep);
251 RedisModule_FreeCallReply(rep);
252 }
253 return REDISMODULE_OK;
254}
255
256/* Call INCR and propagate using RM_Replicate. */
257static int cmdIncrCase2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
258 if (argc != 2)
259 return RedisModule_WrongArity(ctx);
260
261 RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "s", argv[1]);
262 if (!rep) {
263 RedisModule_ReplyWithError(ctx, "NULL reply returned");
264 } else {
265 RedisModule_ReplyWithCallReply(ctx, rep);
266 RedisModule_FreeCallReply(rep);
267 }
268 RedisModule_Replicate(ctx, "INCR", "s", argv[1]);
269 return REDISMODULE_OK;
270}
271
272/* Call INCR and propagate using RM_ReplicateVerbatim. */
273static int cmdIncrCase3(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
274 if (argc != 2)
275 return RedisModule_WrongArity(ctx);
276
277 RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "s", argv[1]);
278 if (!rep) {
279 RedisModule_ReplyWithError(ctx, "NULL reply returned");
280 } else {
281 RedisModule_ReplyWithCallReply(ctx, rep);
282 RedisModule_FreeCallReply(rep);
283 }
284 RedisModule_ReplicateVerbatim(ctx);
285 return REDISMODULE_OK;
286}
287
288static int cmdIncrDels(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
289 REDISMODULE_NOT_USED(argv);
290 REDISMODULE_NOT_USED(argc);
291 dels++;
292 return RedisModule_ReplyWithSimpleString(ctx, "OK");
293}
294
295static int cmdGetDels(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
296 REDISMODULE_NOT_USED(argv);
297 REDISMODULE_NOT_USED(argc);
298 return RedisModule_ReplyWithLongLong(ctx, dels);
299}
300
301static RedisModuleNotificationFunc get_callback_for_event(int event_mask) {
302 switch(event_mask) {
303 case REDISMODULE_NOTIFY_LOADED:
304 return KeySpace_NotificationLoaded;
305 case REDISMODULE_NOTIFY_GENERIC:
306 return KeySpace_NotificationGeneric;
307 case REDISMODULE_NOTIFY_EXPIRED:
308 return KeySpace_NotificationExpired;
309 case REDISMODULE_NOTIFY_MODULE:
310 return KeySpace_NotificationModule;
311 case REDISMODULE_NOTIFY_KEY_MISS:
312 return KeySpace_NotificationModuleKeyMiss;
313 case REDISMODULE_NOTIFY_STRING:
314 // We have two callbacks for STRING events in your OnLoad,
315 // For simplicity, pick the first:
316 return KeySpace_NotificationModuleString;
317 default:
318 return NULL;
319 }
320}
321
322int GetCallbackCountCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
323 REDISMODULE_NOT_USED(argv);
324 REDISMODULE_NOT_USED(argc);
325 RedisModule_ReplyWithLongLong(ctx, callback_call_count);
326 return REDISMODULE_OK;
327}
328
329static int CmdUnsub(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
330 if (argc != 2) {
331 return RedisModule_WrongArity(ctx);
332 }
333
334 long long event_mask;
335 if (RedisModule_StringToLongLong(argv[1], &event_mask) != REDISMODULE_OK) {
336 return RedisModule_ReplyWithError(ctx, "ERR invalid event mask");
337 }
338
339 RedisModuleNotificationFunc cb = get_callback_for_event((int)event_mask);
340 if (cb == NULL) {
341 return RedisModule_ReplyWithError(ctx, "ERR unknown event mask");
342 }
343
344 if (RedisModule_UnsubscribeFromKeyspaceEvents(ctx, (int)event_mask, cb) != REDISMODULE_OK) {
345 return RedisModule_ReplyWithError(ctx, "ERR unsubscribe failed");
346 }
347
348 return RedisModule_ReplyWithSimpleString(ctx, "OK");
349}
350/* This function must be present on each Redis module. It is used in order to
351 * register the commands into the Redis server. */
352int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
353 if (RedisModule_Init(ctx,"testkeyspace",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR){
354 return REDISMODULE_ERR;
355 }
356
357 loaded_event_log = RedisModule_CreateDict(ctx);
358 module_event_log = RedisModule_CreateDict(ctx);
359
360 int keySpaceAll = RedisModule_GetKeyspaceNotificationFlagsAll();
361
362 if (!(keySpaceAll & REDISMODULE_NOTIFY_LOADED)) {
363 // REDISMODULE_NOTIFY_LOADED event are not supported we can not start
364 return REDISMODULE_ERR;
365 }
366
367 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_LOADED, KeySpace_NotificationLoaded) != REDISMODULE_OK){
368 return REDISMODULE_ERR;
369 }
370
371 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_GENERIC, KeySpace_NotificationGeneric) != REDISMODULE_OK){
372 return REDISMODULE_ERR;
373 }
374
375 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_EXPIRED, KeySpace_NotificationExpired) != REDISMODULE_OK){
376 return REDISMODULE_ERR;
377 }
378
379 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_MODULE, KeySpace_NotificationModule) != REDISMODULE_OK){
380 return REDISMODULE_ERR;
381 }
382
383 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_KEY_MISS, KeySpace_NotificationModuleKeyMiss) != REDISMODULE_OK){
384 return REDISMODULE_ERR;
385 }
386
387 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_STRING, KeySpace_NotificationModuleString) != REDISMODULE_OK){
388 return REDISMODULE_ERR;
389 }
390
391 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_STRING, KeySpace_NotificationModuleStringPostNotificationJob) != REDISMODULE_OK){
392 return REDISMODULE_ERR;
393 }
394
395 if (RedisModule_CreateCommand(ctx,"keyspace.notify", cmdNotify,"",0,0,0) == REDISMODULE_ERR){
396 return REDISMODULE_ERR;
397 }
398
399 if (RedisModule_CreateCommand(ctx,"keyspace.is_module_key_notified", cmdIsModuleKeyNotified,"",0,0,0) == REDISMODULE_ERR){
400 return REDISMODULE_ERR;
401 }
402
403 if (RedisModule_CreateCommand(ctx,"keyspace.is_key_loaded", cmdIsKeyLoaded,"",0,0,0) == REDISMODULE_ERR){
404 return REDISMODULE_ERR;
405 }
406
407 if (RedisModule_CreateCommand(ctx, "keyspace.del_key_copy", cmdDelKeyCopy,
408 "write", 0, 0, 0) == REDISMODULE_ERR){
409 return REDISMODULE_ERR;
410 }
411
412 if (RedisModule_CreateCommand(ctx, "keyspace.incr_case1", cmdIncrCase1,
413 "write", 0, 0, 0) == REDISMODULE_ERR){
414 return REDISMODULE_ERR;
415 }
416
417 if (RedisModule_CreateCommand(ctx, "keyspace.incr_case2", cmdIncrCase2,
418 "write", 0, 0, 0) == REDISMODULE_ERR){
419 return REDISMODULE_ERR;
420 }
421
422 if (RedisModule_CreateCommand(ctx, "keyspace.incr_case3", cmdIncrCase3,
423 "write", 0, 0, 0) == REDISMODULE_ERR){
424 return REDISMODULE_ERR;
425 }
426
427 if (RedisModule_CreateCommand(ctx, "keyspace.incr_dels", cmdIncrDels,
428 "write", 0, 0, 0) == REDISMODULE_ERR){
429 return REDISMODULE_ERR;
430 }
431
432 if (RedisModule_CreateCommand(ctx, "keyspace.get_dels", cmdGetDels,
433 "readonly", 0, 0, 0) == REDISMODULE_ERR){
434 return REDISMODULE_ERR;
435 }
436
437 if (RedisModule_CreateCommand(ctx, "keyspace.unsubscribe", CmdUnsub, "write", 0, 0, 0) == REDISMODULE_ERR){
438 return REDISMODULE_ERR;
439 }
440
441 if (RedisModule_CreateCommand(ctx, "keyspace.callback_count", GetCallbackCountCommand, "", 0, 0, 0)== REDISMODULE_ERR){
442 return REDISMODULE_ERR;
443 }
444
445 if (argc == 1) {
446 const char *ptr = RedisModule_StringPtrLen(argv[0], NULL);
447 if (!strcasecmp(ptr, "noload")) {
448 /* This is a hint that we return ERR at the last moment of OnLoad. */
449 RedisModule_FreeDict(ctx, loaded_event_log);
450 RedisModule_FreeDict(ctx, module_event_log);
451 return REDISMODULE_ERR;
452 }
453 }
454
455 return REDISMODULE_OK;
456}
457
458int RedisModule_OnUnload(RedisModuleCtx *ctx) {
459 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(loaded_event_log, "^", NULL, 0);
460 char* key;
461 size_t keyLen;
462 RedisModuleString* val;
463 while((key = RedisModule_DictNextC(iter, &keyLen, (void**)&val))){
464 RedisModule_FreeString(ctx, val);
465 }
466 RedisModule_FreeDict(ctx, loaded_event_log);
467 RedisModule_DictIteratorStop(iter);
468 loaded_event_log = NULL;
469
470 iter = RedisModule_DictIteratorStartC(module_event_log, "^", NULL, 0);
471 while((key = RedisModule_DictNextC(iter, &keyLen, (void**)&val))){
472 RedisModule_FreeString(ctx, val);
473 }
474 RedisModule_FreeDict(ctx, module_event_log);
475 RedisModule_DictIteratorStop(iter);
476 module_event_log = NULL;
477
478 return REDISMODULE_OK;
479}
diff --git a/examples/redis-unstable/tests/modules/keyspecs.c b/examples/redis-unstable/tests/modules/keyspecs.c
new file mode 100644
index 0000000..0a70de8
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/keyspecs.c
@@ -0,0 +1,236 @@
1#include "redismodule.h"
2
3#define UNUSED(V) ((void) V)
4
5/* This function implements all commands in this module. All we care about is
6 * the COMMAND metadata anyway. */
7int kspec_impl(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
8 UNUSED(argv);
9 UNUSED(argc);
10
11 /* Handle getkeys-api introspection (for "kspec.nonewithgetkeys") */
12 if (RedisModule_IsKeysPositionRequest(ctx)) {
13 for (int i = 1; i < argc; i += 2)
14 RedisModule_KeyAtPosWithFlags(ctx, i, REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS);
15
16 return REDISMODULE_OK;
17 }
18
19 RedisModule_ReplyWithSimpleString(ctx, "OK");
20 return REDISMODULE_OK;
21}
22
23int createKspecNone(RedisModuleCtx *ctx) {
24 /* A command without keyspecs; only the legacy (first,last,step) triple (MSET like spec). */
25 if (RedisModule_CreateCommand(ctx,"kspec.none",kspec_impl,"",1,-1,2) == REDISMODULE_ERR)
26 return REDISMODULE_ERR;
27 return REDISMODULE_OK;
28}
29
30int createKspecNoneWithGetkeys(RedisModuleCtx *ctx) {
31 /* A command without keyspecs; only the legacy (first,last,step) triple (MSET like spec), but also has a getkeys callback */
32 if (RedisModule_CreateCommand(ctx,"kspec.nonewithgetkeys",kspec_impl,"getkeys-api",1,-1,2) == REDISMODULE_ERR)
33 return REDISMODULE_ERR;
34 return REDISMODULE_OK;
35}
36
37int createKspecTwoRanges(RedisModuleCtx *ctx) {
38 /* Test that two position/range-based key specs are combined to produce the
39 * legacy (first,last,step) values representing both keys. */
40 if (RedisModule_CreateCommand(ctx,"kspec.tworanges",kspec_impl,"",0,0,0) == REDISMODULE_ERR)
41 return REDISMODULE_ERR;
42
43 RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.tworanges");
44 RedisModuleCommandInfo info = {
45 .version = REDISMODULE_COMMAND_INFO_VERSION,
46 .arity = -2,
47 .key_specs = (RedisModuleCommandKeySpec[]){
48 {
49 .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
50 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
51 .bs.index.pos = 1,
52 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
53 .fk.range = {0,1,0}
54 },
55 {
56 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
57 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
58 .bs.index.pos = 2,
59 /* Omitted find_keys_type is shorthand for RANGE {0,1,0} */
60 },
61 {0}
62 }
63 };
64 if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
65 return REDISMODULE_ERR;
66
67 return REDISMODULE_OK;
68}
69
70int createKspecTwoRangesWithGap(RedisModuleCtx *ctx) {
71 /* Test that two position/range-based key specs are combined to produce the
72 * legacy (first,last,step) values representing just one key. */
73 if (RedisModule_CreateCommand(ctx,"kspec.tworangeswithgap",kspec_impl,"",0,0,0) == REDISMODULE_ERR)
74 return REDISMODULE_ERR;
75
76 RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.tworangeswithgap");
77 RedisModuleCommandInfo info = {
78 .version = REDISMODULE_COMMAND_INFO_VERSION,
79 .arity = -2,
80 .key_specs = (RedisModuleCommandKeySpec[]){
81 {
82 .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
83 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
84 .bs.index.pos = 1,
85 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
86 .fk.range = {0,1,0}
87 },
88 {
89 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
90 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
91 .bs.index.pos = 3,
92 /* Omitted find_keys_type is shorthand for RANGE {0,1,0} */
93 },
94 {0}
95 }
96 };
97 if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
98 return REDISMODULE_ERR;
99
100 return REDISMODULE_OK;
101}
102
103int createKspecKeyword(RedisModuleCtx *ctx) {
104 /* Only keyword-based specs. The legacy triple is wiped and set to (0,0,0). */
105 if (RedisModule_CreateCommand(ctx,"kspec.keyword",kspec_impl,"",3,-1,1) == REDISMODULE_ERR)
106 return REDISMODULE_ERR;
107
108 RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.keyword");
109 RedisModuleCommandInfo info = {
110 .version = REDISMODULE_COMMAND_INFO_VERSION,
111 .key_specs = (RedisModuleCommandKeySpec[]){
112 {
113 .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
114 .begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
115 .bs.keyword.keyword = "KEYS",
116 .bs.keyword.startfrom = 1,
117 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
118 .fk.range = {-1,1,0}
119 },
120 {0}
121 }
122 };
123 if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
124 return REDISMODULE_ERR;
125
126 return REDISMODULE_OK;
127}
128
129int createKspecComplex1(RedisModuleCtx *ctx) {
130 /* First is a range a single key. The rest are keyword-based specs. */
131 if (RedisModule_CreateCommand(ctx,"kspec.complex1",kspec_impl,"",1,1,1) == REDISMODULE_ERR)
132 return REDISMODULE_ERR;
133
134 RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.complex1");
135 RedisModuleCommandInfo info = {
136 .version = REDISMODULE_COMMAND_INFO_VERSION,
137 .key_specs = (RedisModuleCommandKeySpec[]){
138 {
139 .flags = REDISMODULE_CMD_KEY_RO,
140 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
141 .bs.index.pos = 1,
142 },
143 {
144 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
145 .begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
146 .bs.keyword.keyword = "STORE",
147 .bs.keyword.startfrom = 2,
148 },
149 {
150 .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
151 .begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
152 .bs.keyword.keyword = "KEYS",
153 .bs.keyword.startfrom = 2,
154 .find_keys_type = REDISMODULE_KSPEC_FK_KEYNUM,
155 .fk.keynum = {0,1,1}
156 },
157 {0}
158 }
159 };
160 if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
161 return REDISMODULE_ERR;
162
163 return REDISMODULE_OK;
164}
165
166int createKspecComplex2(RedisModuleCtx *ctx) {
167 /* First is not legacy, more than STATIC_KEYS_SPECS_NUM specs */
168 if (RedisModule_CreateCommand(ctx,"kspec.complex2",kspec_impl,"",0,0,0) == REDISMODULE_ERR)
169 return REDISMODULE_ERR;
170
171 RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.complex2");
172 RedisModuleCommandInfo info = {
173 .version = REDISMODULE_COMMAND_INFO_VERSION,
174 .key_specs = (RedisModuleCommandKeySpec[]){
175 {
176 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
177 .begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
178 .bs.keyword.keyword = "STORE",
179 .bs.keyword.startfrom = 5,
180 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
181 .fk.range = {0,1,0}
182 },
183 {
184 .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
185 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
186 .bs.index.pos = 1,
187 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
188 .fk.range = {0,1,0}
189 },
190 {
191 .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
192 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
193 .bs.index.pos = 2,
194 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
195 .fk.range = {0,1,0}
196 },
197 {
198 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
199 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
200 .bs.index.pos = 3,
201 .find_keys_type = REDISMODULE_KSPEC_FK_KEYNUM,
202 .fk.keynum = {0,1,1}
203 },
204 {
205 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
206 .begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
207 .bs.keyword.keyword = "MOREKEYS",
208 .bs.keyword.startfrom = 5,
209 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
210 .fk.range = {-1,1,0}
211 },
212 {0}
213 }
214 };
215 if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
216 return REDISMODULE_ERR;
217
218 return REDISMODULE_OK;
219}
220
221int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
222 REDISMODULE_NOT_USED(argv);
223 REDISMODULE_NOT_USED(argc);
224
225 if (RedisModule_Init(ctx, "keyspecs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
226 return REDISMODULE_ERR;
227
228 if (createKspecNone(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
229 if (createKspecNoneWithGetkeys(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
230 if (createKspecTwoRanges(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
231 if (createKspecTwoRangesWithGap(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
232 if (createKspecKeyword(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
233 if (createKspecComplex1(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
234 if (createKspecComplex2(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
235 return REDISMODULE_OK;
236}
diff --git a/examples/redis-unstable/tests/modules/list.c b/examples/redis-unstable/tests/modules/list.c
new file mode 100644
index 0000000..401b2d8
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/list.c
@@ -0,0 +1,252 @@
1#include "redismodule.h"
2#include <assert.h>
3#include <errno.h>
4#include <strings.h>
5
6/* LIST.GETALL key [REVERSE] */
7int list_getall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
8 if (argc < 2 || argc > 3) return RedisModule_WrongArity(ctx);
9 int reverse = (argc == 3 &&
10 !strcasecmp(RedisModule_StringPtrLen(argv[2], NULL),
11 "REVERSE"));
12 RedisModule_AutoMemory(ctx);
13 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
14 if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST) {
15 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
16 }
17 long n = RedisModule_ValueLength(key);
18 RedisModule_ReplyWithArray(ctx, n);
19 if (!reverse) {
20 for (long i = 0; i < n; i++) {
21 RedisModuleString *elem = RedisModule_ListGet(key, i);
22 RedisModule_ReplyWithString(ctx, elem);
23 RedisModule_FreeString(ctx, elem);
24 }
25 } else {
26 for (long i = -1; i >= -n; i--) {
27 RedisModuleString *elem = RedisModule_ListGet(key, i);
28 RedisModule_ReplyWithString(ctx, elem);
29 RedisModule_FreeString(ctx, elem);
30 }
31 }
32
33 /* Test error condition: index out of bounds */
34 assert(RedisModule_ListGet(key, n) == NULL);
35 assert(errno == EDOM); /* no more elements in list */
36
37 /* RedisModule_CloseKey(key); //implicit, done by auto memory */
38 return REDISMODULE_OK;
39}
40
41/* LIST.EDIT key [REVERSE] cmdstr [value ..]
42 *
43 * cmdstr is a string of the following characters:
44 *
45 * k -- keep
46 * d -- delete
47 * i -- insert value from args
48 * r -- replace with value from args
49 *
50 * The number of occurrences of "i" and "r" in cmdstr) should correspond to the
51 * number of args after cmdstr.
52 *
53 * Reply with a RESP3 Map, containing the number of edits (inserts, replaces, deletes)
54 * performed, as well as the last index and the entry it points to.
55 */
56int list_edit(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
57 if (argc < 3) return RedisModule_WrongArity(ctx);
58 RedisModule_AutoMemory(ctx);
59 int argpos = 1; /* the next arg */
60
61 /* key */
62 int keymode = REDISMODULE_READ | REDISMODULE_WRITE;
63 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[argpos++], keymode);
64 if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST) {
65 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
66 }
67
68 /* REVERSE */
69 int reverse = 0;
70 if (argc >= 4 &&
71 !strcasecmp(RedisModule_StringPtrLen(argv[argpos], NULL), "REVERSE")) {
72 reverse = 1;
73 argpos++;
74 }
75
76 /* cmdstr */
77 size_t cmdstr_len;
78 const char *cmdstr = RedisModule_StringPtrLen(argv[argpos++], &cmdstr_len);
79
80 /* validate cmdstr vs. argc */
81 long num_req_args = 0;
82 long min_list_length = 0;
83 for (size_t cmdpos = 0; cmdpos < cmdstr_len; cmdpos++) {
84 char c = cmdstr[cmdpos];
85 if (c == 'i' || c == 'r') num_req_args++;
86 if (c == 'd' || c == 'r' || c == 'k') min_list_length++;
87 }
88 if (argc < argpos + num_req_args) {
89 return RedisModule_ReplyWithError(ctx, "ERR too few args");
90 }
91 if ((long)RedisModule_ValueLength(key) < min_list_length) {
92 return RedisModule_ReplyWithError(ctx, "ERR list too short");
93 }
94
95 /* Iterate over the chars in cmdstr (edit instructions) */
96 long long num_inserts = 0, num_deletes = 0, num_replaces = 0;
97 long index = reverse ? -1 : 0;
98 RedisModuleString *value;
99
100 for (size_t cmdpos = 0; cmdpos < cmdstr_len; cmdpos++) {
101 switch (cmdstr[cmdpos]) {
102 case 'i': /* insert */
103 value = argv[argpos++];
104 assert(RedisModule_ListInsert(key, index, value) == REDISMODULE_OK);
105 index += reverse ? -1 : 1;
106 num_inserts++;
107 break;
108 case 'd': /* delete */
109 assert(RedisModule_ListDelete(key, index) == REDISMODULE_OK);
110 num_deletes++;
111 break;
112 case 'r': /* replace */
113 value = argv[argpos++];
114 assert(RedisModule_ListSet(key, index, value) == REDISMODULE_OK);
115 index += reverse ? -1 : 1;
116 num_replaces++;
117 break;
118 case 'k': /* keep */
119 index += reverse ? -1 : 1;
120 break;
121 }
122 }
123
124 RedisModuleString *v = RedisModule_ListGet(key, index);
125 RedisModule_ReplyWithMap(ctx, v ? 5 : 4);
126 RedisModule_ReplyWithCString(ctx, "i");
127 RedisModule_ReplyWithLongLong(ctx, num_inserts);
128 RedisModule_ReplyWithCString(ctx, "d");
129 RedisModule_ReplyWithLongLong(ctx, num_deletes);
130 RedisModule_ReplyWithCString(ctx, "r");
131 RedisModule_ReplyWithLongLong(ctx, num_replaces);
132 RedisModule_ReplyWithCString(ctx, "index");
133 RedisModule_ReplyWithLongLong(ctx, index);
134 if (v) {
135 RedisModule_ReplyWithCString(ctx, "entry");
136 RedisModule_ReplyWithString(ctx, v);
137 RedisModule_FreeString(ctx, v);
138 }
139
140 RedisModule_CloseKey(key);
141 return REDISMODULE_OK;
142}
143
144/* Reply based on errno as set by the List API functions. */
145static int replyByErrno(RedisModuleCtx *ctx) {
146 switch (errno) {
147 case EDOM:
148 return RedisModule_ReplyWithError(ctx, "ERR index out of bounds");
149 case ENOTSUP:
150 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
151 default: assert(0); /* Can't happen */
152 }
153}
154
155/* LIST.GET key index */
156int list_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
157 if (argc != 3) return RedisModule_WrongArity(ctx);
158 long long index;
159 if (RedisModule_StringToLongLong(argv[2], &index) != REDISMODULE_OK) {
160 return RedisModule_ReplyWithError(ctx, "ERR index must be a number");
161 }
162 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
163 RedisModuleString *value = RedisModule_ListGet(key, index);
164 if (value) {
165 RedisModule_ReplyWithString(ctx, value);
166 RedisModule_FreeString(ctx, value);
167 } else {
168 replyByErrno(ctx);
169 }
170 RedisModule_CloseKey(key);
171 return REDISMODULE_OK;
172}
173
174/* LIST.SET key index value */
175int list_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
176 if (argc != 4) return RedisModule_WrongArity(ctx);
177 long long index;
178 if (RedisModule_StringToLongLong(argv[2], &index) != REDISMODULE_OK) {
179 RedisModule_ReplyWithError(ctx, "ERR index must be a number");
180 return REDISMODULE_OK;
181 }
182 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
183 if (RedisModule_ListSet(key, index, argv[3]) == REDISMODULE_OK) {
184 RedisModule_ReplyWithSimpleString(ctx, "OK");
185 } else {
186 replyByErrno(ctx);
187 }
188 RedisModule_CloseKey(key);
189 return REDISMODULE_OK;
190}
191
192/* LIST.INSERT key index value
193 *
194 * If index is negative, value is inserted after, otherwise before the element
195 * at index.
196 */
197int list_insert(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
198 if (argc != 4) return RedisModule_WrongArity(ctx);
199 long long index;
200 if (RedisModule_StringToLongLong(argv[2], &index) != REDISMODULE_OK) {
201 RedisModule_ReplyWithError(ctx, "ERR index must be a number");
202 return REDISMODULE_OK;
203 }
204 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
205 if (RedisModule_ListInsert(key, index, argv[3]) == REDISMODULE_OK) {
206 RedisModule_ReplyWithSimpleString(ctx, "OK");
207 } else {
208 replyByErrno(ctx);
209 }
210 RedisModule_CloseKey(key);
211 return REDISMODULE_OK;
212}
213
214/* LIST.DELETE key index */
215int list_delete(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
216 if (argc != 3) return RedisModule_WrongArity(ctx);
217 long long index;
218 if (RedisModule_StringToLongLong(argv[2], &index) != REDISMODULE_OK) {
219 RedisModule_ReplyWithError(ctx, "ERR index must be a number");
220 return REDISMODULE_OK;
221 }
222 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
223 if (RedisModule_ListDelete(key, index) == REDISMODULE_OK) {
224 RedisModule_ReplyWithSimpleString(ctx, "OK");
225 } else {
226 replyByErrno(ctx);
227 }
228 RedisModule_CloseKey(key);
229 return REDISMODULE_OK;
230}
231
232int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
233 REDISMODULE_NOT_USED(argv);
234 REDISMODULE_NOT_USED(argc);
235 if (RedisModule_Init(ctx, "list", 1, REDISMODULE_APIVER_1) == REDISMODULE_OK &&
236 RedisModule_CreateCommand(ctx, "list.getall", list_getall, "",
237 1, 1, 1) == REDISMODULE_OK &&
238 RedisModule_CreateCommand(ctx, "list.edit", list_edit, "write",
239 1, 1, 1) == REDISMODULE_OK &&
240 RedisModule_CreateCommand(ctx, "list.get", list_get, "write",
241 1, 1, 1) == REDISMODULE_OK &&
242 RedisModule_CreateCommand(ctx, "list.set", list_set, "write",
243 1, 1, 1) == REDISMODULE_OK &&
244 RedisModule_CreateCommand(ctx, "list.insert", list_insert, "write",
245 1, 1, 1) == REDISMODULE_OK &&
246 RedisModule_CreateCommand(ctx, "list.delete", list_delete, "write",
247 1, 1, 1) == REDISMODULE_OK) {
248 return REDISMODULE_OK;
249 } else {
250 return REDISMODULE_ERR;
251 }
252}
diff --git a/examples/redis-unstable/tests/modules/mallocsize.c b/examples/redis-unstable/tests/modules/mallocsize.c
new file mode 100644
index 0000000..a1d31c1
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/mallocsize.c
@@ -0,0 +1,237 @@
1#include "redismodule.h"
2#include <string.h>
3#include <assert.h>
4#include <unistd.h>
5
6#define UNUSED(V) ((void) V)
7
8/* Registered type */
9RedisModuleType *mallocsize_type = NULL;
10
11typedef enum {
12 UDT_RAW,
13 UDT_STRING,
14 UDT_DICT
15} udt_type_t;
16
17typedef struct {
18 void *ptr;
19 size_t len;
20} raw_t;
21
22typedef struct {
23 udt_type_t type;
24 union {
25 raw_t raw;
26 RedisModuleString *str;
27 RedisModuleDict *dict;
28 } data;
29} udt_t;
30
31void udt_free(void *value) {
32 udt_t *udt = value;
33 switch (udt->type) {
34 case (UDT_RAW): {
35 RedisModule_Free(udt->data.raw.ptr);
36 break;
37 }
38 case (UDT_STRING): {
39 RedisModule_FreeString(NULL, udt->data.str);
40 break;
41 }
42 case (UDT_DICT): {
43 RedisModuleString *dk, *dv;
44 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(udt->data.dict, "^", NULL, 0);
45 while((dk = RedisModule_DictNext(NULL, iter, (void **)&dv)) != NULL) {
46 RedisModule_FreeString(NULL, dk);
47 RedisModule_FreeString(NULL, dv);
48 }
49 RedisModule_DictIteratorStop(iter);
50 RedisModule_FreeDict(NULL, udt->data.dict);
51 break;
52 }
53 }
54 RedisModule_Free(udt);
55}
56
57void udt_rdb_save(RedisModuleIO *rdb, void *value) {
58 udt_t *udt = value;
59 RedisModule_SaveUnsigned(rdb, udt->type);
60 switch (udt->type) {
61 case (UDT_RAW): {
62 RedisModule_SaveStringBuffer(rdb, udt->data.raw.ptr, udt->data.raw.len);
63 break;
64 }
65 case (UDT_STRING): {
66 RedisModule_SaveString(rdb, udt->data.str);
67 break;
68 }
69 case (UDT_DICT): {
70 RedisModule_SaveUnsigned(rdb, RedisModule_DictSize(udt->data.dict));
71 RedisModuleString *dk, *dv;
72 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(udt->data.dict, "^", NULL, 0);
73 while((dk = RedisModule_DictNext(NULL, iter, (void **)&dv)) != NULL) {
74 RedisModule_SaveString(rdb, dk);
75 RedisModule_SaveString(rdb, dv);
76 RedisModule_FreeString(NULL, dk); /* Allocated by RedisModule_DictNext */
77 }
78 RedisModule_DictIteratorStop(iter);
79 break;
80 }
81 }
82}
83
84void *udt_rdb_load(RedisModuleIO *rdb, int encver) {
85 if (encver != 0)
86 return NULL;
87 udt_t *udt = RedisModule_Alloc(sizeof(*udt));
88 udt->type = RedisModule_LoadUnsigned(rdb);
89 switch (udt->type) {
90 case (UDT_RAW): {
91 udt->data.raw.ptr = RedisModule_LoadStringBuffer(rdb, &udt->data.raw.len);
92 break;
93 }
94 case (UDT_STRING): {
95 udt->data.str = RedisModule_LoadString(rdb);
96 break;
97 }
98 case (UDT_DICT): {
99 long long dict_len = RedisModule_LoadUnsigned(rdb);
100 udt->data.dict = RedisModule_CreateDict(NULL);
101 for (int i = 0; i < dict_len; i += 2) {
102 RedisModuleString *key = RedisModule_LoadString(rdb);
103 RedisModuleString *val = RedisModule_LoadString(rdb);
104 RedisModule_DictSet(udt->data.dict, key, val);
105 }
106 break;
107 }
108 }
109
110 return udt;
111}
112
113size_t udt_mem_usage(RedisModuleKeyOptCtx *ctx, const void *value, size_t sample_size) {
114 UNUSED(ctx);
115 UNUSED(sample_size);
116
117 const udt_t *udt = value;
118 size_t size = sizeof(*udt);
119
120 switch (udt->type) {
121 case (UDT_RAW): {
122 size += RedisModule_MallocSize(udt->data.raw.ptr);
123 break;
124 }
125 case (UDT_STRING): {
126 size += RedisModule_MallocSizeString(udt->data.str);
127 break;
128 }
129 case (UDT_DICT): {
130 void *dk;
131 size_t keylen;
132 RedisModuleString *dv;
133 RedisModuleDictIter *iter = RedisModule_DictIteratorStartC(udt->data.dict, "^", NULL, 0);
134 while((dk = RedisModule_DictNextC(iter, &keylen, (void **)&dv)) != NULL) {
135 size += keylen;
136 size += RedisModule_MallocSizeString(dv);
137 }
138 RedisModule_DictIteratorStop(iter);
139 break;
140 }
141 }
142
143 return size;
144}
145
146/* MALLOCSIZE.SETRAW key len */
147int cmd_setraw(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
148 if (argc != 3)
149 return RedisModule_WrongArity(ctx);
150
151 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
152
153 udt_t *udt = RedisModule_Alloc(sizeof(*udt));
154 udt->type = UDT_RAW;
155
156 long long raw_len;
157 RedisModule_StringToLongLong(argv[2], &raw_len);
158 udt->data.raw.ptr = RedisModule_Alloc(raw_len);
159 udt->data.raw.len = raw_len;
160
161 RedisModule_ModuleTypeSetValue(key, mallocsize_type, udt);
162 RedisModule_CloseKey(key);
163
164 return RedisModule_ReplyWithSimpleString(ctx, "OK");
165}
166
167/* MALLOCSIZE.SETSTR key string */
168int cmd_setstr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
169 if (argc != 3)
170 return RedisModule_WrongArity(ctx);
171
172 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
173
174 udt_t *udt = RedisModule_Alloc(sizeof(*udt));
175 udt->type = UDT_STRING;
176
177 udt->data.str = argv[2];
178 RedisModule_RetainString(ctx, argv[2]);
179
180 RedisModule_ModuleTypeSetValue(key, mallocsize_type, udt);
181 RedisModule_CloseKey(key);
182
183 return RedisModule_ReplyWithSimpleString(ctx, "OK");
184}
185
186/* MALLOCSIZE.SETDICT key field value [field value ...] */
187int cmd_setdict(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
188 if (argc < 4 || argc % 2)
189 return RedisModule_WrongArity(ctx);
190
191 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
192
193 udt_t *udt = RedisModule_Alloc(sizeof(*udt));
194 udt->type = UDT_DICT;
195
196 udt->data.dict = RedisModule_CreateDict(ctx);
197 for (int i = 2; i < argc; i += 2) {
198 RedisModule_DictSet(udt->data.dict, argv[i], argv[i+1]);
199 /* No need to retain argv[i], it is copied as the rax key */
200 RedisModule_RetainString(ctx, argv[i+1]);
201 }
202
203 RedisModule_ModuleTypeSetValue(key, mallocsize_type, udt);
204 RedisModule_CloseKey(key);
205
206 return RedisModule_ReplyWithSimpleString(ctx, "OK");
207}
208
209int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
210 UNUSED(argv);
211 UNUSED(argc);
212 if (RedisModule_Init(ctx,"mallocsize",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
213 return REDISMODULE_ERR;
214
215 RedisModuleTypeMethods tm = {
216 .version = REDISMODULE_TYPE_METHOD_VERSION,
217 .rdb_load = udt_rdb_load,
218 .rdb_save = udt_rdb_save,
219 .free = udt_free,
220 .mem_usage2 = udt_mem_usage,
221 };
222
223 mallocsize_type = RedisModule_CreateDataType(ctx, "allocsize", 0, &tm);
224 if (mallocsize_type == NULL)
225 return REDISMODULE_ERR;
226
227 if (RedisModule_CreateCommand(ctx, "mallocsize.setraw", cmd_setraw, "", 1, 1, 1) == REDISMODULE_ERR)
228 return REDISMODULE_ERR;
229
230 if (RedisModule_CreateCommand(ctx, "mallocsize.setstr", cmd_setstr, "", 1, 1, 1) == REDISMODULE_ERR)
231 return REDISMODULE_ERR;
232
233 if (RedisModule_CreateCommand(ctx, "mallocsize.setdict", cmd_setdict, "", 1, 1, 1) == REDISMODULE_ERR)
234 return REDISMODULE_ERR;
235
236 return REDISMODULE_OK;
237}
diff --git a/examples/redis-unstable/tests/modules/misc.c b/examples/redis-unstable/tests/modules/misc.c
new file mode 100644
index 0000000..dbf0fec
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/misc.c
@@ -0,0 +1,642 @@
1#include "redismodule.h"
2
3#include <string.h>
4#include <assert.h>
5#include <unistd.h>
6#include <errno.h>
7#include <limits.h>
8
9#define UNUSED(x) (void)(x)
10
11static int n_events = 0;
12
13static int KeySpace_NotificationModuleKeyMissExpired(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
14 UNUSED(ctx);
15 UNUSED(type);
16 UNUSED(event);
17 UNUSED(key);
18 n_events++;
19 return REDISMODULE_OK;
20}
21
22int test_clear_n_events(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
23 UNUSED(argv);
24 UNUSED(argc);
25 n_events = 0;
26 RedisModule_ReplyWithSimpleString(ctx, "OK");
27 return REDISMODULE_OK;
28}
29
30int test_get_n_events(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
31 UNUSED(argv);
32 UNUSED(argc);
33 RedisModule_ReplyWithLongLong(ctx, n_events);
34 return REDISMODULE_OK;
35}
36
37int test_open_key_no_effects(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
38 if (argc<2) {
39 RedisModule_WrongArity(ctx);
40 return REDISMODULE_OK;
41 }
42
43 int supportedMode = RedisModule_GetOpenKeyModesAll();
44 if (!(supportedMode & REDISMODULE_READ) || !(supportedMode & REDISMODULE_OPEN_KEY_NOEFFECTS)) {
45 RedisModule_ReplyWithError(ctx, "OpenKey modes are not supported");
46 return REDISMODULE_OK;
47 }
48
49 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_OPEN_KEY_NOEFFECTS);
50 if (!key) {
51 RedisModule_ReplyWithError(ctx, "key not found");
52 return REDISMODULE_OK;
53 }
54
55 RedisModule_CloseKey(key);
56 RedisModule_ReplyWithSimpleString(ctx, "OK");
57 return REDISMODULE_OK;
58}
59
60int test_call_generic(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
61{
62 if (argc<2) {
63 RedisModule_WrongArity(ctx);
64 return REDISMODULE_OK;
65 }
66
67 const char* cmdname = RedisModule_StringPtrLen(argv[1], NULL);
68 RedisModuleCallReply *reply = RedisModule_Call(ctx, cmdname, "v", argv+2, (size_t)argc-2);
69 if (reply) {
70 RedisModule_ReplyWithCallReply(ctx, reply);
71 RedisModule_FreeCallReply(reply);
72 } else {
73 RedisModule_ReplyWithError(ctx, strerror(errno));
74 }
75 return REDISMODULE_OK;
76}
77
78int test_call_info(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
79{
80 RedisModuleCallReply *reply;
81 if (argc>1)
82 reply = RedisModule_Call(ctx, "info", "s", argv[1]);
83 else
84 reply = RedisModule_Call(ctx, "info", "");
85 if (reply) {
86 RedisModule_ReplyWithCallReply(ctx, reply);
87 RedisModule_FreeCallReply(reply);
88 } else {
89 RedisModule_ReplyWithError(ctx, strerror(errno));
90 }
91 return REDISMODULE_OK;
92}
93
94int test_ld_conv(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
95 UNUSED(argv);
96 UNUSED(argc);
97 long double ld = 0.00000000000000001L;
98 const char *ldstr = "0.00000000000000001";
99 RedisModuleString *s1 = RedisModule_CreateStringFromLongDouble(ctx, ld, 1);
100 RedisModuleString *s2 =
101 RedisModule_CreateString(ctx, ldstr, strlen(ldstr));
102 if (RedisModule_StringCompare(s1, s2) != 0) {
103 char err[4096];
104 snprintf(err, 4096,
105 "Failed to convert long double to string ('%s' != '%s')",
106 RedisModule_StringPtrLen(s1, NULL),
107 RedisModule_StringPtrLen(s2, NULL));
108 RedisModule_ReplyWithError(ctx, err);
109 goto final;
110 }
111 long double ld2 = 0;
112 if (RedisModule_StringToLongDouble(s2, &ld2) == REDISMODULE_ERR) {
113 RedisModule_ReplyWithError(ctx,
114 "Failed to convert string to long double");
115 goto final;
116 }
117 if (ld2 != ld) {
118 char err[4096];
119 snprintf(err, 4096,
120 "Failed to convert string to long double (%.40Lf != %.40Lf)",
121 ld2,
122 ld);
123 RedisModule_ReplyWithError(ctx, err);
124 goto final;
125 }
126
127 /* Make sure we can't convert a string that has \0 in it */
128 char buf[4] = "123";
129 buf[1] = '\0';
130 RedisModuleString *s3 = RedisModule_CreateString(ctx, buf, 3);
131 long double ld3;
132 if (RedisModule_StringToLongDouble(s3, &ld3) == REDISMODULE_OK) {
133 RedisModule_ReplyWithError(ctx, "Invalid string successfully converted to long double");
134 RedisModule_FreeString(ctx, s3);
135 goto final;
136 }
137 RedisModule_FreeString(ctx, s3);
138
139 RedisModule_ReplyWithLongDouble(ctx, ld2);
140final:
141 RedisModule_FreeString(ctx, s1);
142 RedisModule_FreeString(ctx, s2);
143 return REDISMODULE_OK;
144}
145
146int test_flushall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
147{
148 REDISMODULE_NOT_USED(argv);
149 REDISMODULE_NOT_USED(argc);
150 RedisModule_ResetDataset(1, 0);
151 RedisModule_ReplyWithCString(ctx, "Ok");
152 return REDISMODULE_OK;
153}
154
155int test_dbsize(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
156{
157 REDISMODULE_NOT_USED(argv);
158 REDISMODULE_NOT_USED(argc);
159 long long ll = RedisModule_DbSize(ctx);
160 RedisModule_ReplyWithLongLong(ctx, ll);
161 return REDISMODULE_OK;
162}
163
164int test_randomkey(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
165{
166 REDISMODULE_NOT_USED(argv);
167 REDISMODULE_NOT_USED(argc);
168 RedisModuleString *str = RedisModule_RandomKey(ctx);
169 RedisModule_ReplyWithString(ctx, str);
170 RedisModule_FreeString(ctx, str);
171 return REDISMODULE_OK;
172}
173
174int test_keyexists(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
175 if (argc < 2) return RedisModule_WrongArity(ctx);
176 RedisModuleString *key = argv[1];
177 int exists = RedisModule_KeyExists(ctx, key);
178 return RedisModule_ReplyWithBool(ctx, exists);
179}
180
181RedisModuleKey *open_key_or_reply(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) {
182 RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, mode);
183 if (!key) {
184 RedisModule_ReplyWithError(ctx, "key not found");
185 return NULL;
186 }
187 return key;
188}
189
190int test_getlru(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
191{
192 if (argc<2) {
193 RedisModule_WrongArity(ctx);
194 return REDISMODULE_OK;
195 }
196 RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
197 mstime_t lru;
198 RedisModule_GetLRU(key, &lru);
199 RedisModule_ReplyWithLongLong(ctx, lru);
200 RedisModule_CloseKey(key);
201 return REDISMODULE_OK;
202}
203
204int test_setlru(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
205{
206 if (argc<3) {
207 RedisModule_WrongArity(ctx);
208 return REDISMODULE_OK;
209 }
210 RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
211 mstime_t lru;
212 if (RedisModule_StringToLongLong(argv[2], &lru) != REDISMODULE_OK) {
213 RedisModule_ReplyWithError(ctx, "invalid idle time");
214 return REDISMODULE_OK;
215 }
216 int was_set = RedisModule_SetLRU(key, lru)==REDISMODULE_OK;
217 RedisModule_ReplyWithLongLong(ctx, was_set);
218 RedisModule_CloseKey(key);
219 return REDISMODULE_OK;
220}
221
222int test_getlfu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
223{
224 if (argc<2) {
225 RedisModule_WrongArity(ctx);
226 return REDISMODULE_OK;
227 }
228 RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
229 mstime_t lfu;
230 RedisModule_GetLFU(key, &lfu);
231 RedisModule_ReplyWithLongLong(ctx, lfu);
232 RedisModule_CloseKey(key);
233 return REDISMODULE_OK;
234}
235
236int test_setlfu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
237{
238 if (argc<3) {
239 RedisModule_WrongArity(ctx);
240 return REDISMODULE_OK;
241 }
242 RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
243 mstime_t lfu;
244 if (RedisModule_StringToLongLong(argv[2], &lfu) != REDISMODULE_OK) {
245 RedisModule_ReplyWithError(ctx, "invalid freq");
246 return REDISMODULE_OK;
247 }
248 int was_set = RedisModule_SetLFU(key, lfu)==REDISMODULE_OK;
249 RedisModule_ReplyWithLongLong(ctx, was_set);
250 RedisModule_CloseKey(key);
251 return REDISMODULE_OK;
252}
253
254int test_redisversion(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
255 (void) argv;
256 (void) argc;
257
258 int version = RedisModule_GetServerVersion();
259 int patch = version & 0x000000ff;
260 int minor = (version & 0x0000ff00) >> 8;
261 int major = (version & 0x00ff0000) >> 16;
262
263 RedisModuleString* vStr = RedisModule_CreateStringPrintf(ctx, "%d.%d.%d", major, minor, patch);
264 RedisModule_ReplyWithString(ctx, vStr);
265 RedisModule_FreeString(ctx, vStr);
266
267 return REDISMODULE_OK;
268}
269
270int test_getclientcert(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
271{
272 (void) argv;
273 (void) argc;
274
275 RedisModuleString *cert = RedisModule_GetClientCertificate(ctx,
276 RedisModule_GetClientId(ctx));
277 if (!cert) {
278 RedisModule_ReplyWithNull(ctx);
279 } else {
280 RedisModule_ReplyWithString(ctx, cert);
281 RedisModule_FreeString(ctx, cert);
282 }
283
284 return REDISMODULE_OK;
285}
286
287int test_clientinfo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
288{
289 (void) argv;
290 (void) argc;
291
292 RedisModuleClientInfoV1 ci = REDISMODULE_CLIENTINFO_INITIALIZER_V1;
293 uint64_t client_id = RedisModule_GetClientId(ctx);
294
295 /* Check expected result from the V1 initializer. */
296 assert(ci.version == 1);
297 /* Trying to populate a future version of the struct should fail. */
298 ci.version = REDISMODULE_CLIENTINFO_VERSION + 1;
299 assert(RedisModule_GetClientInfoById(&ci, client_id) == REDISMODULE_ERR);
300
301 ci.version = 1;
302 if (RedisModule_GetClientInfoById(&ci, client_id) == REDISMODULE_ERR) {
303 RedisModule_ReplyWithError(ctx, "failed to get client info");
304 return REDISMODULE_OK;
305 }
306
307 RedisModule_ReplyWithArray(ctx, 10);
308 char flags[512];
309 snprintf(flags, sizeof(flags) - 1, "%s:%s:%s:%s:%s:%s",
310 ci.flags & REDISMODULE_CLIENTINFO_FLAG_SSL ? "ssl" : "",
311 ci.flags & REDISMODULE_CLIENTINFO_FLAG_PUBSUB ? "pubsub" : "",
312 ci.flags & REDISMODULE_CLIENTINFO_FLAG_BLOCKED ? "blocked" : "",
313 ci.flags & REDISMODULE_CLIENTINFO_FLAG_TRACKING ? "tracking" : "",
314 ci.flags & REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET ? "unixsocket" : "",
315 ci.flags & REDISMODULE_CLIENTINFO_FLAG_MULTI ? "multi" : "");
316
317 RedisModule_ReplyWithCString(ctx, "flags");
318 RedisModule_ReplyWithCString(ctx, flags);
319 RedisModule_ReplyWithCString(ctx, "id");
320 RedisModule_ReplyWithLongLong(ctx, ci.id);
321 RedisModule_ReplyWithCString(ctx, "addr");
322 RedisModule_ReplyWithCString(ctx, ci.addr);
323 RedisModule_ReplyWithCString(ctx, "port");
324 RedisModule_ReplyWithLongLong(ctx, ci.port);
325 RedisModule_ReplyWithCString(ctx, "db");
326 RedisModule_ReplyWithLongLong(ctx, ci.db);
327
328 return REDISMODULE_OK;
329}
330
331int test_getname(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
332 (void)argv;
333 if (argc != 1) return RedisModule_WrongArity(ctx);
334 unsigned long long id = RedisModule_GetClientId(ctx);
335 RedisModuleString *name = RedisModule_GetClientNameById(ctx, id);
336 if (name == NULL)
337 return RedisModule_ReplyWithError(ctx, "-ERR No name");
338 RedisModule_ReplyWithString(ctx, name);
339 RedisModule_FreeString(ctx, name);
340 return REDISMODULE_OK;
341}
342
343int test_setname(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
344 if (argc != 2) return RedisModule_WrongArity(ctx);
345 unsigned long long id = RedisModule_GetClientId(ctx);
346 if (RedisModule_SetClientNameById(id, argv[1]) == REDISMODULE_OK)
347 return RedisModule_ReplyWithSimpleString(ctx, "OK");
348 else
349 return RedisModule_ReplyWithError(ctx, strerror(errno));
350}
351
352int test_log_tsctx(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
353{
354 RedisModuleCtx *tsctx = RedisModule_GetDetachedThreadSafeContext(ctx);
355
356 if (argc != 3) {
357 RedisModule_WrongArity(ctx);
358 return REDISMODULE_OK;
359 }
360
361 char level[50];
362 size_t level_len;
363 const char *level_str = RedisModule_StringPtrLen(argv[1], &level_len);
364 snprintf(level, sizeof(level) - 1, "%.*s", (int) level_len, level_str);
365
366 size_t msg_len;
367 const char *msg_str = RedisModule_StringPtrLen(argv[2], &msg_len);
368
369 RedisModule_Log(tsctx, level, "%.*s", (int) msg_len, msg_str);
370 RedisModule_FreeThreadSafeContext(tsctx);
371
372 RedisModule_ReplyWithSimpleString(ctx, "OK");
373 return REDISMODULE_OK;
374}
375
376int test_weird_cmd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
377 REDISMODULE_NOT_USED(argv);
378 REDISMODULE_NOT_USED(argc);
379
380 RedisModule_ReplyWithSimpleString(ctx, "OK");
381 return REDISMODULE_OK;
382}
383
384int test_monotonic_time(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
385 REDISMODULE_NOT_USED(argv);
386 REDISMODULE_NOT_USED(argc);
387
388 RedisModule_ReplyWithLongLong(ctx, RedisModule_MonotonicMicroseconds());
389 return REDISMODULE_OK;
390}
391
392/* wrapper for RM_Call */
393int test_rm_call(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
394 if(argc < 2){
395 return RedisModule_WrongArity(ctx);
396 }
397
398 const char* cmd = RedisModule_StringPtrLen(argv[1], NULL);
399
400 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "Ev", argv + 2, (size_t)argc - 2);
401 if(!rep){
402 RedisModule_ReplyWithError(ctx, "NULL reply returned");
403 }else{
404 RedisModule_ReplyWithCallReply(ctx, rep);
405 RedisModule_FreeCallReply(rep);
406 }
407
408 return REDISMODULE_OK;
409}
410
411/* wrapper for RM_Call which also replicates the module command */
412int test_rm_call_replicate(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
413 test_rm_call(ctx, argv, argc);
414 RedisModule_ReplicateVerbatim(ctx);
415
416 return REDISMODULE_OK;
417}
418
419/* wrapper for RM_Call with flags */
420int test_rm_call_flags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
421 if(argc < 3){
422 return RedisModule_WrongArity(ctx);
423 }
424
425 /* Append Ev to the provided flags. */
426 RedisModuleString *flags = RedisModule_CreateStringFromString(ctx, argv[1]);
427 RedisModule_StringAppendBuffer(ctx, flags, "Ev", 2);
428
429 const char* flg = RedisModule_StringPtrLen(flags, NULL);
430 const char* cmd = RedisModule_StringPtrLen(argv[2], NULL);
431
432 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, flg, argv + 3, (size_t)argc - 3);
433 if(!rep){
434 RedisModule_ReplyWithError(ctx, "NULL reply returned");
435 }else{
436 RedisModule_ReplyWithCallReply(ctx, rep);
437 RedisModule_FreeCallReply(rep);
438 }
439 RedisModule_FreeString(ctx, flags);
440
441 return REDISMODULE_OK;
442}
443
444int test_ull_conv(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
445 UNUSED(argv);
446 UNUSED(argc);
447 unsigned long long ull = 18446744073709551615ULL;
448 const char *ullstr = "18446744073709551615";
449
450 RedisModuleString *s1 = RedisModule_CreateStringFromULongLong(ctx, ull);
451 RedisModuleString *s2 =
452 RedisModule_CreateString(ctx, ullstr, strlen(ullstr));
453 if (RedisModule_StringCompare(s1, s2) != 0) {
454 char err[4096];
455 snprintf(err, 4096,
456 "Failed to convert unsigned long long to string ('%s' != '%s')",
457 RedisModule_StringPtrLen(s1, NULL),
458 RedisModule_StringPtrLen(s2, NULL));
459 RedisModule_ReplyWithError(ctx, err);
460 goto final;
461 }
462 unsigned long long ull2 = 0;
463 if (RedisModule_StringToULongLong(s2, &ull2) == REDISMODULE_ERR) {
464 RedisModule_ReplyWithError(ctx,
465 "Failed to convert string to unsigned long long");
466 goto final;
467 }
468 if (ull2 != ull) {
469 char err[4096];
470 snprintf(err, 4096,
471 "Failed to convert string to unsigned long long (%llu != %llu)",
472 ull2,
473 ull);
474 RedisModule_ReplyWithError(ctx, err);
475 goto final;
476 }
477
478 /* Make sure we can't convert a string more than ULLONG_MAX or less than 0 */
479 ullstr = "18446744073709551616";
480 RedisModuleString *s3 = RedisModule_CreateString(ctx, ullstr, strlen(ullstr));
481 unsigned long long ull3;
482 if (RedisModule_StringToULongLong(s3, &ull3) == REDISMODULE_OK) {
483 RedisModule_ReplyWithError(ctx, "Invalid string successfully converted to unsigned long long");
484 RedisModule_FreeString(ctx, s3);
485 goto final;
486 }
487 RedisModule_FreeString(ctx, s3);
488 ullstr = "-1";
489 RedisModuleString *s4 = RedisModule_CreateString(ctx, ullstr, strlen(ullstr));
490 unsigned long long ull4;
491 if (RedisModule_StringToULongLong(s4, &ull4) == REDISMODULE_OK) {
492 RedisModule_ReplyWithError(ctx, "Invalid string successfully converted to unsigned long long");
493 RedisModule_FreeString(ctx, s4);
494 goto final;
495 }
496 RedisModule_FreeString(ctx, s4);
497
498 RedisModule_ReplyWithSimpleString(ctx, "ok");
499
500final:
501 RedisModule_FreeString(ctx, s1);
502 RedisModule_FreeString(ctx, s2);
503 return REDISMODULE_OK;
504}
505
506int test_malloc_api(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
507 UNUSED(argv);
508 UNUSED(argc);
509
510 void *p;
511
512 p = RedisModule_TryAlloc(1024);
513 memset(p, 0, 1024);
514 RedisModule_Free(p);
515
516 p = RedisModule_TryCalloc(1, 1024);
517 memset(p, 1, 1024);
518
519 p = RedisModule_TryRealloc(p, 5 * 1024);
520 memset(p, 1, 5 * 1024);
521 RedisModule_Free(p);
522
523 RedisModule_ReplyWithSimpleString(ctx, "OK");
524 return REDISMODULE_OK;
525}
526
527int test_keyslot(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
528 /* Static check of the ClusterKeySlot + ClusterKeySlotC + ClusterCanonicalKeyNameInSlot
529 * round-trip for all slots. */
530 for (unsigned int slot = 0; slot < 16384; slot++) {
531 const char *tag = RedisModule_ClusterCanonicalKeyNameInSlot(slot);
532 RedisModuleString *key = RedisModule_CreateStringPrintf(ctx, "x{%s}y", tag);
533 assert(slot == RedisModule_ClusterKeySlot(key));
534 size_t len;
535 const char *key_c = RedisModule_StringPtrLen(key, &len);
536 assert(slot == RedisModule_ClusterKeySlotC(key_c, len));
537 RedisModule_FreeString(ctx, key);
538 }
539 if (argc != 2){
540 return RedisModule_WrongArity(ctx);
541 }
542 unsigned int slot = RedisModule_ClusterKeySlot(argv[1]);
543 return RedisModule_ReplyWithLongLong(ctx, slot);
544}
545
546int only_reply_ok(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
547 REDISMODULE_NOT_USED(argv);
548 REDISMODULE_NOT_USED(argc);
549
550 RedisModule_ReplyWithSimpleString(ctx, "OK");
551 return REDISMODULE_OK;
552}
553
554/* Test command for RM_SignalModifiedKey. */
555int test_signalmodifiedkey(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
556 if (argc != 2) return RedisModule_WrongArity(ctx);
557
558 /* Manually signal that the key was modified */
559 RedisModule_SignalModifiedKey(ctx, argv[1]);
560 RedisModule_ReplyWithSimpleString(ctx, "OK");
561 return REDISMODULE_OK;
562}
563
564int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
565 REDISMODULE_NOT_USED(argv);
566 REDISMODULE_NOT_USED(argc);
567 if (RedisModule_Init(ctx,"misc",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
568 return REDISMODULE_ERR;
569
570 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_KEY_MISS | REDISMODULE_NOTIFY_EXPIRED, KeySpace_NotificationModuleKeyMissExpired) != REDISMODULE_OK){
571 return REDISMODULE_ERR;
572 }
573
574 if (RedisModule_CreateCommand(ctx,"test.call_generic", test_call_generic,"",0,0,0) == REDISMODULE_ERR)
575 return REDISMODULE_ERR;
576 if (RedisModule_CreateCommand(ctx,"test.call_info", test_call_info,"",0,0,0) == REDISMODULE_ERR)
577 return REDISMODULE_ERR;
578 if (RedisModule_CreateCommand(ctx,"test.ld_conversion", test_ld_conv, "",0,0,0) == REDISMODULE_ERR)
579 return REDISMODULE_ERR;
580 if (RedisModule_CreateCommand(ctx,"test.ull_conversion", test_ull_conv, "",0,0,0) == REDISMODULE_ERR)
581 return REDISMODULE_ERR;
582 if (RedisModule_CreateCommand(ctx,"test.flushall", test_flushall,"",0,0,0) == REDISMODULE_ERR)
583 return REDISMODULE_ERR;
584 if (RedisModule_CreateCommand(ctx,"test.dbsize", test_dbsize,"",0,0,0) == REDISMODULE_ERR)
585 return REDISMODULE_ERR;
586 if (RedisModule_CreateCommand(ctx,"test.randomkey", test_randomkey,"",0,0,0) == REDISMODULE_ERR)
587 return REDISMODULE_ERR;
588 if (RedisModule_CreateCommand(ctx,"test.keyexists", test_keyexists,"",1,1,1) == REDISMODULE_ERR)
589 return REDISMODULE_ERR;
590 if (RedisModule_CreateCommand(ctx,"test.setlru", test_setlru,"",0,0,0) == REDISMODULE_ERR)
591 return REDISMODULE_ERR;
592 if (RedisModule_CreateCommand(ctx,"test.getlru", test_getlru,"",0,0,0) == REDISMODULE_ERR)
593 return REDISMODULE_ERR;
594 if (RedisModule_CreateCommand(ctx,"test.setlfu", test_setlfu,"",0,0,0) == REDISMODULE_ERR)
595 return REDISMODULE_ERR;
596 if (RedisModule_CreateCommand(ctx,"test.getlfu", test_getlfu,"",0,0,0) == REDISMODULE_ERR)
597 return REDISMODULE_ERR;
598 if (RedisModule_CreateCommand(ctx,"test.clientinfo", test_clientinfo,"",0,0,0) == REDISMODULE_ERR)
599 return REDISMODULE_ERR;
600 if (RedisModule_CreateCommand(ctx,"test.getname", test_getname,"",0,0,0) == REDISMODULE_ERR)
601 return REDISMODULE_ERR;
602 if (RedisModule_CreateCommand(ctx,"test.setname", test_setname,"",0,0,0) == REDISMODULE_ERR)
603 return REDISMODULE_ERR;
604 if (RedisModule_CreateCommand(ctx,"test.redisversion", test_redisversion,"",0,0,0) == REDISMODULE_ERR)
605 return REDISMODULE_ERR;
606 if (RedisModule_CreateCommand(ctx,"test.getclientcert", test_getclientcert,"",0,0,0) == REDISMODULE_ERR)
607 return REDISMODULE_ERR;
608 if (RedisModule_CreateCommand(ctx,"test.log_tsctx", test_log_tsctx,"",0,0,0) == REDISMODULE_ERR)
609 return REDISMODULE_ERR;
610 /* Add a command with ':' in it's name, so that we can check commandstats sanitization. */
611 if (RedisModule_CreateCommand(ctx,"test.weird:cmd", test_weird_cmd,"readonly",0,0,0) == REDISMODULE_ERR)
612 return REDISMODULE_ERR;
613 if (RedisModule_CreateCommand(ctx,"test.monotonic_time", test_monotonic_time,"",0,0,0) == REDISMODULE_ERR)
614 return REDISMODULE_ERR;
615 if (RedisModule_CreateCommand(ctx, "test.rm_call", test_rm_call,"allow-stale", 0, 0, 0) == REDISMODULE_ERR)
616 return REDISMODULE_ERR;
617 if (RedisModule_CreateCommand(ctx, "test.rm_call_flags", test_rm_call_flags,"allow-stale", 0, 0, 0) == REDISMODULE_ERR)
618 return REDISMODULE_ERR;
619 if (RedisModule_CreateCommand(ctx, "test.rm_call_replicate", test_rm_call_replicate,"allow-stale", 0, 0, 0) == REDISMODULE_ERR)
620 return REDISMODULE_ERR;
621 if (RedisModule_CreateCommand(ctx, "test.silent_open_key", test_open_key_no_effects,"", 0, 0, 0) == REDISMODULE_ERR)
622 return REDISMODULE_ERR;
623 if (RedisModule_CreateCommand(ctx, "test.get_n_events", test_get_n_events,"", 0, 0, 0) == REDISMODULE_ERR)
624 return REDISMODULE_ERR;
625 if (RedisModule_CreateCommand(ctx, "test.clear_n_events", test_clear_n_events,"", 0, 0, 0) == REDISMODULE_ERR)
626 return REDISMODULE_ERR;
627 if (RedisModule_CreateCommand(ctx, "test.malloc_api", test_malloc_api,"", 0, 0, 0) == REDISMODULE_ERR)
628 return REDISMODULE_ERR;
629 if (RedisModule_CreateCommand(ctx, "test.keyslot", test_keyslot, "", 0, 0, 0) == REDISMODULE_ERR)
630 return REDISMODULE_ERR;
631 if (RedisModule_CreateCommand(ctx, "test.incompatible_cluster_cmd", only_reply_ok, "", 1, -1, 2) == REDISMODULE_ERR)
632 return REDISMODULE_ERR;
633 if (RedisModule_CreateCommand(ctx, "test.no_cluster_cmd", NULL, "no-cluster", 0, 0, 0) == REDISMODULE_ERR)
634 return REDISMODULE_ERR;
635 RedisModuleCommand *parent = RedisModule_GetCommand(ctx, "test.no_cluster_cmd");
636 if (RedisModule_CreateSubcommand(parent, "set", only_reply_ok, "no-cluster", 0, 0, 0) == REDISMODULE_ERR)
637 return REDISMODULE_ERR;
638 if (RedisModule_CreateCommand(ctx, "test.signalmodifiedkey", test_signalmodifiedkey, "write", 1, 1, 1) == REDISMODULE_ERR)
639 return REDISMODULE_ERR;
640
641 return REDISMODULE_OK;
642}
diff --git a/examples/redis-unstable/tests/modules/moduleauthtwo.c b/examples/redis-unstable/tests/modules/moduleauthtwo.c
new file mode 100644
index 0000000..0a4f56b
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/moduleauthtwo.c
@@ -0,0 +1,43 @@
1#include "redismodule.h"
2
3#include <string.h>
4
5/* This is a second sample module to validate that module authentication callbacks can be registered
6 * from multiple modules. */
7
8/* Non Blocking Module Auth callback / implementation. */
9int auth_cb(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err) {
10 const char *user = RedisModule_StringPtrLen(username, NULL);
11 const char *pwd = RedisModule_StringPtrLen(password, NULL);
12 if (!strcmp(user,"foo") && !strcmp(pwd,"allow_two")) {
13 RedisModule_AuthenticateClientWithACLUser(ctx, "foo", 3, NULL, NULL, NULL);
14 return REDISMODULE_AUTH_HANDLED;
15 }
16 else if (!strcmp(user,"foo") && !strcmp(pwd,"deny_two")) {
17 RedisModuleString *log = RedisModule_CreateString(ctx, "Module Auth", 11);
18 RedisModule_ACLAddLogEntryByUserName(ctx, username, log, REDISMODULE_ACL_LOG_AUTH);
19 RedisModule_FreeString(ctx, log);
20 const char *err_msg = "Auth denied by Misc Module.";
21 *err = RedisModule_CreateString(ctx, err_msg, strlen(err_msg));
22 return REDISMODULE_AUTH_HANDLED;
23 }
24 return REDISMODULE_AUTH_NOT_HANDLED;
25}
26
27int test_rm_register_auth_cb(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
28 REDISMODULE_NOT_USED(argv);
29 REDISMODULE_NOT_USED(argc);
30 RedisModule_RegisterAuthCallback(ctx, auth_cb);
31 RedisModule_ReplyWithSimpleString(ctx, "OK");
32 return REDISMODULE_OK;
33}
34
35int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
36 REDISMODULE_NOT_USED(argv);
37 REDISMODULE_NOT_USED(argc);
38 if (RedisModule_Init(ctx,"moduleauthtwo",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
39 return REDISMODULE_ERR;
40 if (RedisModule_CreateCommand(ctx,"testmoduletwo.rm_register_auth_cb", test_rm_register_auth_cb,"",0,0,0) == REDISMODULE_ERR)
41 return REDISMODULE_ERR;
42 return REDISMODULE_OK;
43} \ No newline at end of file
diff --git a/examples/redis-unstable/tests/modules/moduleconfigs.c b/examples/redis-unstable/tests/modules/moduleconfigs.c
new file mode 100644
index 0000000..04974f7
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/moduleconfigs.c
@@ -0,0 +1,287 @@
1#include "redismodule.h"
2#include <strings.h>
3int mutable_bool_val, no_prefix_bool, no_prefix_bool2;
4int immutable_bool_val;
5long long longval, no_prefix_longval;
6long long memval, no_prefix_memval;
7RedisModuleString *strval = NULL;
8RedisModuleString *strval2 = NULL;
9int enumval, no_prefix_enumval;
10int flagsval;
11
12/* Series of get and set callbacks for each type of config, these rely on the privdata ptr
13 * to point to the config, and they register the configs as such. Note that one could also just
14 * use names if they wanted, and store anything in privdata. */
15int getBoolConfigCommand(const char *name, void *privdata) {
16 REDISMODULE_NOT_USED(name);
17 return (*(int *)privdata);
18}
19
20int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
21 REDISMODULE_NOT_USED(name);
22 REDISMODULE_NOT_USED(err);
23 *(int *)privdata = new;
24 return REDISMODULE_OK;
25}
26
27long long getNumericConfigCommand(const char *name, void *privdata) {
28 REDISMODULE_NOT_USED(name);
29 return (*(long long *) privdata);
30}
31
32int setNumericConfigCommand(const char *name, long long new, void *privdata, RedisModuleString **err) {
33 REDISMODULE_NOT_USED(name);
34 REDISMODULE_NOT_USED(err);
35 *(long long *)privdata = new;
36 return REDISMODULE_OK;
37}
38
39RedisModuleString *getStringConfigCommand(const char *name, void *privdata) {
40 REDISMODULE_NOT_USED(name);
41 REDISMODULE_NOT_USED(privdata);
42 return strval;
43}
44int setStringConfigCommand(const char *name, RedisModuleString *new, void *privdata, RedisModuleString **err) {
45 REDISMODULE_NOT_USED(name);
46 REDISMODULE_NOT_USED(err);
47 REDISMODULE_NOT_USED(privdata);
48 size_t len;
49 if (!strcasecmp(RedisModule_StringPtrLen(new, &len), "rejectisfreed")) {
50 *err = RedisModule_CreateString(NULL, "Cannot set string to 'rejectisfreed'", 36);
51 return REDISMODULE_ERR;
52 }
53 if (strval) RedisModule_FreeString(NULL, strval);
54 RedisModule_RetainString(NULL, new);
55 strval = new;
56 return REDISMODULE_OK;
57}
58
59int getEnumConfigCommand(const char *name, void *privdata) {
60 REDISMODULE_NOT_USED(name);
61 REDISMODULE_NOT_USED(privdata);
62 return enumval;
63}
64
65int setEnumConfigCommand(const char *name, int val, void *privdata, RedisModuleString **err) {
66 REDISMODULE_NOT_USED(name);
67 REDISMODULE_NOT_USED(err);
68 REDISMODULE_NOT_USED(privdata);
69 enumval = val;
70 return REDISMODULE_OK;
71}
72
73int getFlagsConfigCommand(const char *name, void *privdata) {
74 REDISMODULE_NOT_USED(name);
75 REDISMODULE_NOT_USED(privdata);
76 return flagsval;
77}
78
79int setFlagsConfigCommand(const char *name, int val, void *privdata, RedisModuleString **err) {
80 REDISMODULE_NOT_USED(name);
81 REDISMODULE_NOT_USED(err);
82 REDISMODULE_NOT_USED(privdata);
83 flagsval = val;
84 return REDISMODULE_OK;
85}
86
87int boolApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
88 REDISMODULE_NOT_USED(ctx);
89 REDISMODULE_NOT_USED(privdata);
90 if (mutable_bool_val && immutable_bool_val) {
91 *err = RedisModule_CreateString(NULL, "Bool configs cannot both be yes.", 32);
92 return REDISMODULE_ERR;
93 }
94 return REDISMODULE_OK;
95}
96
97int longlongApplyFunc(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err) {
98 REDISMODULE_NOT_USED(ctx);
99 REDISMODULE_NOT_USED(privdata);
100 if (longval == memval) {
101 *err = RedisModule_CreateString(NULL, "These configs cannot equal each other.", 38);
102 return REDISMODULE_ERR;
103 }
104 return REDISMODULE_OK;
105}
106
107RedisModuleString *getStringConfigUnprefix(const char *name, void *privdata) {
108 REDISMODULE_NOT_USED(name);
109 REDISMODULE_NOT_USED(privdata);
110 return strval2;
111}
112
113int setStringConfigUnprefix(const char *name, RedisModuleString *new, void *privdata, RedisModuleString **err) {
114 REDISMODULE_NOT_USED(name);
115 REDISMODULE_NOT_USED(err);
116 REDISMODULE_NOT_USED(privdata);
117 if (strval2) RedisModule_FreeString(NULL, strval2);
118 RedisModule_RetainString(NULL, new);
119 strval2 = new;
120 return REDISMODULE_OK;
121}
122
123int getEnumConfigUnprefix(const char *name, void *privdata) {
124 REDISMODULE_NOT_USED(name);
125 REDISMODULE_NOT_USED(privdata);
126 return no_prefix_enumval;
127}
128
129int setEnumConfigUnprefix(const char *name, int val, void *privdata, RedisModuleString **err) {
130 REDISMODULE_NOT_USED(name);
131 REDISMODULE_NOT_USED(err);
132 REDISMODULE_NOT_USED(privdata);
133 no_prefix_enumval = val;
134 return REDISMODULE_OK;
135}
136
137int registerBlockCheck(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
138 REDISMODULE_NOT_USED(argv);
139 REDISMODULE_NOT_USED(argc);
140 int response_ok = 0;
141 int result = RedisModule_RegisterBoolConfig(ctx, "mutable_bool", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &mutable_bool_val);
142 response_ok |= (result == REDISMODULE_OK);
143
144 result = RedisModule_RegisterStringConfig(ctx, "string", "secret password", REDISMODULE_CONFIG_DEFAULT, getStringConfigCommand, setStringConfigCommand, NULL, NULL);
145 response_ok |= (result == REDISMODULE_OK);
146
147 const char *enum_vals[] = {"none", "five", "one", "two", "four"};
148 const int int_vals[] = {0, 5, 1, 2, 4};
149 result = RedisModule_RegisterEnumConfig(ctx, "enum", 1, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 5, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL);
150 response_ok |= (result == REDISMODULE_OK);
151
152 result = RedisModule_RegisterNumericConfig(ctx, "numeric", -1, REDISMODULE_CONFIG_DEFAULT, -5, 2000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &longval);
153 response_ok |= (result == REDISMODULE_OK);
154
155 result = RedisModule_LoadConfigs(ctx);
156 response_ok |= (result == REDISMODULE_OK);
157
158 /* This validates that it's not possible to register/load configs outside OnLoad,
159 * thus returns an error if they succeed. */
160 if (response_ok) {
161 RedisModule_ReplyWithError(ctx, "UNEXPECTEDOK");
162 } else {
163 RedisModule_ReplyWithSimpleString(ctx, "OK");
164 }
165 return REDISMODULE_OK;
166}
167
168void cleanup(RedisModuleCtx *ctx) {
169 if (strval) {
170 RedisModule_FreeString(ctx, strval);
171 strval = NULL;
172 }
173 if (strval2) {
174 RedisModule_FreeString(ctx, strval2);
175 strval2 = NULL;
176 }
177}
178
179int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
180 REDISMODULE_NOT_USED(argv);
181 REDISMODULE_NOT_USED(argc);
182
183 if (RedisModule_Init(ctx, "moduleconfigs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
184 RedisModule_Log(ctx, "warning", "Failed to init module");
185 return REDISMODULE_ERR;
186 }
187
188 if (RedisModule_RegisterBoolConfig(ctx, "mutable_bool", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &mutable_bool_val) == REDISMODULE_ERR) {
189 RedisModule_Log(ctx, "warning", "Failed to register mutable_bool");
190 return REDISMODULE_ERR;
191 }
192 /* Immutable config here. */
193 if (RedisModule_RegisterBoolConfig(ctx, "immutable_bool", 0, REDISMODULE_CONFIG_IMMUTABLE, getBoolConfigCommand, setBoolConfigCommand, boolApplyFunc, &immutable_bool_val) == REDISMODULE_ERR) {
194 RedisModule_Log(ctx, "warning", "Failed to register immutable_bool");
195 return REDISMODULE_ERR;
196 }
197 if (RedisModule_RegisterStringConfig(ctx, "string", "secret password", REDISMODULE_CONFIG_DEFAULT, getStringConfigCommand, setStringConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
198 RedisModule_Log(ctx, "warning", "Failed to register string");
199 return REDISMODULE_ERR;
200 }
201
202 /* On the stack to make sure we're copying them. */
203 const char *enum_vals[] = {"none", "five", "one", "two", "four"};
204 const int int_vals[] = {0, 5, 1, 2, 4};
205
206 if (RedisModule_RegisterEnumConfig(ctx, "enum", 1, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 5, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
207 RedisModule_Log(ctx, "warning", "Failed to register enum");
208 return REDISMODULE_ERR;
209 }
210 if (RedisModule_RegisterEnumConfig(ctx, "flags", 3, REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_BITFLAGS, enum_vals, int_vals, 5, getFlagsConfigCommand, setFlagsConfigCommand, NULL, NULL) == REDISMODULE_ERR) {
211 RedisModule_Log(ctx, "warning", "Failed to register flags");
212 return REDISMODULE_ERR;
213 }
214 /* Memory config here. */
215 if (RedisModule_RegisterNumericConfig(ctx, "memory_numeric", 1024, REDISMODULE_CONFIG_DEFAULT | REDISMODULE_CONFIG_MEMORY, 0, 3000000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &memval) == REDISMODULE_ERR) {
216 RedisModule_Log(ctx, "warning", "Failed to register memory_numeric");
217 return REDISMODULE_ERR;
218 }
219 if (RedisModule_RegisterNumericConfig(ctx, "numeric", -1, REDISMODULE_CONFIG_DEFAULT, -5, 2000, getNumericConfigCommand, setNumericConfigCommand, longlongApplyFunc, &longval) == REDISMODULE_ERR) {
220 RedisModule_Log(ctx, "warning", "Failed to register numeric");
221 return REDISMODULE_ERR;
222 }
223
224 /*** unprefixed and aliased configuration ***/
225 if (RedisModule_RegisterBoolConfig(ctx, "unprefix-bool|unprefix-bool-alias", 1, REDISMODULE_CONFIG_DEFAULT|REDISMODULE_CONFIG_UNPREFIXED,
226 getBoolConfigCommand, setBoolConfigCommand, NULL, &no_prefix_bool) == REDISMODULE_ERR) {
227 RedisModule_Log(ctx, "warning", "Failed to register unprefix-bool");
228 return REDISMODULE_ERR;
229 }
230 if (RedisModule_RegisterBoolConfig(ctx, "unprefix-noalias-bool", 1, REDISMODULE_CONFIG_DEFAULT|REDISMODULE_CONFIG_UNPREFIXED,
231 getBoolConfigCommand, setBoolConfigCommand, NULL, &no_prefix_bool2) == REDISMODULE_ERR) {
232 RedisModule_Log(ctx, "warning", "Failed to register unprefix-noalias-bool");
233 return REDISMODULE_ERR;
234 }
235 if (RedisModule_RegisterNumericConfig(ctx, "unprefix.numeric|unprefix.numeric-alias", -1, REDISMODULE_CONFIG_DEFAULT|REDISMODULE_CONFIG_UNPREFIXED,
236 -5, 2000, getNumericConfigCommand, setNumericConfigCommand, NULL, &no_prefix_longval) == REDISMODULE_ERR) {
237 RedisModule_Log(ctx, "warning", "Failed to register unprefix.numeric");
238 return REDISMODULE_ERR;
239 }
240 if (RedisModule_RegisterStringConfig(ctx, "unprefix-string|unprefix.string-alias", "secret unprefix", REDISMODULE_CONFIG_DEFAULT|REDISMODULE_CONFIG_UNPREFIXED,
241 getStringConfigUnprefix, setStringConfigUnprefix, NULL, NULL) == REDISMODULE_ERR) {
242 RedisModule_Log(ctx, "warning", "Failed to register unprefix-string");
243 return REDISMODULE_ERR;
244 }
245 if (RedisModule_RegisterEnumConfig(ctx, "unprefix-enum|unprefix-enum-alias", 1, REDISMODULE_CONFIG_DEFAULT|REDISMODULE_CONFIG_UNPREFIXED,
246 enum_vals, int_vals, 5, getEnumConfigUnprefix, setEnumConfigUnprefix, NULL, NULL) == REDISMODULE_ERR) {
247 RedisModule_Log(ctx, "warning", "Failed to register unprefix-enum");
248 return REDISMODULE_ERR;
249 }
250
251 RedisModule_Log(ctx, "debug", "Registered configuration");
252 size_t len;
253 if (argc && !strcasecmp(RedisModule_StringPtrLen(argv[0], &len), "noload")) {
254 return REDISMODULE_OK;
255 } else if (argc && !strcasecmp(RedisModule_StringPtrLen(argv[0], &len), "override-default")) {
256 if (RedisModule_LoadDefaultConfigs(ctx) == REDISMODULE_ERR) {
257 RedisModule_Log(ctx, "warning", "Failed to load default configuration");
258 goto err;
259 }
260 // simulate configuration values being overwritten by the command line
261 RedisModule_Log(ctx, "debug", "Overriding configuration values");
262 if (strval) RedisModule_FreeString(ctx, strval);
263 strval = RedisModule_CreateString(ctx, "foo", 3);
264 longval = memval = 123;
265 }
266 RedisModule_Log(ctx, "debug", "Loading configuration");
267 if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
268 RedisModule_Log(ctx, "warning", "Failed to load configuration");
269 goto err;
270 }
271 /* Creates a command which registers configs outside OnLoad() function. */
272 if (RedisModule_CreateCommand(ctx,"block.register.configs.outside.onload", registerBlockCheck, "write", 0, 0, 0) == REDISMODULE_ERR) {
273 RedisModule_Log(ctx, "warning", "Failed to register command");
274 goto err;
275 }
276
277 return REDISMODULE_OK;
278err:
279 cleanup(ctx);
280 return REDISMODULE_ERR;
281}
282
283int RedisModule_OnUnload(RedisModuleCtx *ctx) {
284 REDISMODULE_NOT_USED(ctx);
285 cleanup(ctx);
286 return REDISMODULE_OK;
287}
diff --git a/examples/redis-unstable/tests/modules/moduleconfigstwo.c b/examples/redis-unstable/tests/modules/moduleconfigstwo.c
new file mode 100644
index 0000000..c0e8f91
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/moduleconfigstwo.c
@@ -0,0 +1,39 @@
1#include "redismodule.h"
2#include <strings.h>
3
4/* Second module configs module, for testing.
5 * Need to make sure that multiple modules with configs don't interfere with each other */
6int bool_config;
7
8int getBoolConfigCommand(const char *name, void *privdata) {
9 REDISMODULE_NOT_USED(privdata);
10 if (!strcasecmp(name, "test")) {
11 return bool_config;
12 }
13 return 0;
14}
15
16int setBoolConfigCommand(const char *name, int new, void *privdata, RedisModuleString **err) {
17 REDISMODULE_NOT_USED(privdata);
18 REDISMODULE_NOT_USED(err);
19 if (!strcasecmp(name, "test")) {
20 bool_config = new;
21 return REDISMODULE_OK;
22 }
23 return REDISMODULE_ERR;
24}
25
26/* No arguments are expected */
27int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
28 REDISMODULE_NOT_USED(argv);
29 REDISMODULE_NOT_USED(argc);
30 if (RedisModule_Init(ctx, "configs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) return REDISMODULE_ERR;
31
32 if (RedisModule_RegisterBoolConfig(ctx, "test", 1, REDISMODULE_CONFIG_DEFAULT, getBoolConfigCommand, setBoolConfigCommand, NULL, &argc) == REDISMODULE_ERR) {
33 return REDISMODULE_ERR;
34 }
35 if (RedisModule_LoadConfigs(ctx) == REDISMODULE_ERR) {
36 return REDISMODULE_ERR;
37 }
38 return REDISMODULE_OK;
39} \ No newline at end of file
diff --git a/examples/redis-unstable/tests/modules/postnotifications.c b/examples/redis-unstable/tests/modules/postnotifications.c
new file mode 100644
index 0000000..96fb859
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/postnotifications.c
@@ -0,0 +1,289 @@
1/* This module is used to test the server post keyspace jobs API.
2 *
3 * -----------------------------------------------------------------------------
4 *
5 * Copyright (c) 2020-Present, Redis Ltd.
6 * All rights reserved.
7 *
8 * Licensed under your choice of (a) the Redis Source Available License 2.0
9 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
10 * GNU Affero General Public License v3 (AGPLv3).
11 */
12
13/* This module allow to verify 'RedisModule_AddPostNotificationJob' by registering to 3
14 * key space event:
15 * * STRINGS - the module register to all strings notifications and set post notification job
16 * that increase a counter indicating how many times the string key was changed.
17 * In addition, it increase another counter that counts the total changes that
18 * was made on all strings keys.
19 * * EXPIRED - the module register to expired event and set post notification job that that
20 * counts the total number of expired events.
21 * * EVICTED - the module register to evicted event and set post notification job that that
22 * counts the total number of evicted events.
23 *
24 * In addition, the module register a new command, 'postnotification.async_set', that performs a set
25 * command from a background thread. This allows to check the 'RedisModule_AddPostNotificationJob' on
26 * notifications that was triggered on a background thread. */
27
28#define _BSD_SOURCE
29#define _DEFAULT_SOURCE /* For usleep */
30
31#include "redismodule.h"
32#include <stdio.h>
33#include <string.h>
34#include <unistd.h>
35#include <pthread.h>
36
37static void KeySpace_PostNotificationStringFreePD(void *pd) {
38 RedisModule_FreeString(NULL, pd);
39}
40
41static void KeySpace_PostNotificationReadKey(RedisModuleCtx *ctx, void *pd) {
42 RedisModuleCallReply* rep = RedisModule_Call(ctx, "get", "!s", pd);
43 RedisModule_FreeCallReply(rep);
44}
45
46static void KeySpace_PostNotificationString(RedisModuleCtx *ctx, void *pd) {
47 REDISMODULE_NOT_USED(ctx);
48 RedisModuleCallReply* rep = RedisModule_Call(ctx, "incr", "!s", pd);
49 RedisModule_FreeCallReply(rep);
50}
51
52static int KeySpace_NotificationExpired(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
53 REDISMODULE_NOT_USED(type);
54 REDISMODULE_NOT_USED(event);
55 REDISMODULE_NOT_USED(key);
56
57 RedisModuleString *new_key = RedisModule_CreateString(NULL, "expired", 7);
58 int res = RedisModule_AddPostNotificationJob(ctx, KeySpace_PostNotificationString, new_key, KeySpace_PostNotificationStringFreePD);
59 if (res == REDISMODULE_ERR) KeySpace_PostNotificationStringFreePD(new_key);
60 return REDISMODULE_OK;
61}
62
63static int KeySpace_NotificationEvicted(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
64 REDISMODULE_NOT_USED(type);
65 REDISMODULE_NOT_USED(event);
66 REDISMODULE_NOT_USED(key);
67
68 const char *key_str = RedisModule_StringPtrLen(key, NULL);
69
70 if (strncmp(key_str, "evicted", 7) == 0) {
71 return REDISMODULE_OK; /* do not count the evicted key */
72 }
73
74 if (strncmp(key_str, "before_evicted", 14) == 0) {
75 return REDISMODULE_OK; /* do not count the before_evicted key */
76 }
77
78 RedisModuleString *new_key = RedisModule_CreateString(NULL, "evicted", 7);
79 int res = RedisModule_AddPostNotificationJob(ctx, KeySpace_PostNotificationString, new_key, KeySpace_PostNotificationStringFreePD);
80 if (res == REDISMODULE_ERR) KeySpace_PostNotificationStringFreePD(new_key);
81 return REDISMODULE_OK;
82}
83
84static int KeySpace_NotificationString(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
85 REDISMODULE_NOT_USED(ctx);
86 REDISMODULE_NOT_USED(type);
87 REDISMODULE_NOT_USED(event);
88
89 const char *key_str = RedisModule_StringPtrLen(key, NULL);
90
91 if (strncmp(key_str, "string_", 7) != 0) {
92 return REDISMODULE_OK;
93 }
94
95 if (strcmp(key_str, "string_total") == 0) {
96 return REDISMODULE_OK;
97 }
98
99 RedisModuleString *new_key;
100 if (strncmp(key_str, "string_changed{", 15) == 0) {
101 new_key = RedisModule_CreateString(NULL, "string_total", 12);
102 } else {
103 new_key = RedisModule_CreateStringPrintf(NULL, "string_changed{%s}", key_str);
104 }
105
106 int res = RedisModule_AddPostNotificationJob(ctx, KeySpace_PostNotificationString, new_key, KeySpace_PostNotificationStringFreePD);
107 if (res == REDISMODULE_ERR) KeySpace_PostNotificationStringFreePD(new_key);
108 return REDISMODULE_OK;
109}
110
111static int KeySpace_LazyExpireInsidePostNotificationJob(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
112 REDISMODULE_NOT_USED(ctx);
113 REDISMODULE_NOT_USED(type);
114 REDISMODULE_NOT_USED(event);
115
116 const char *key_str = RedisModule_StringPtrLen(key, NULL);
117
118 if (strncmp(key_str, "read_", 5) != 0) {
119 return REDISMODULE_OK;
120 }
121
122 RedisModuleString *new_key = RedisModule_CreateString(NULL, key_str + 5, strlen(key_str) - 5);;
123 int res = RedisModule_AddPostNotificationJob(ctx, KeySpace_PostNotificationReadKey, new_key, KeySpace_PostNotificationStringFreePD);
124 if (res == REDISMODULE_ERR) KeySpace_PostNotificationStringFreePD(new_key);
125 return REDISMODULE_OK;
126}
127
128static int KeySpace_NestedNotification(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key){
129 REDISMODULE_NOT_USED(ctx);
130 REDISMODULE_NOT_USED(type);
131 REDISMODULE_NOT_USED(event);
132
133 const char *key_str = RedisModule_StringPtrLen(key, NULL);
134
135 if (strncmp(key_str, "write_sync_", 11) != 0) {
136 return REDISMODULE_OK;
137 }
138
139 /* This test was only meant to check REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS.
140 * In general it is wrong and discourage to perform any writes inside a notification callback. */
141 RedisModuleString *new_key = RedisModule_CreateString(NULL, key_str + 11, strlen(key_str) - 11);;
142 RedisModuleCallReply* rep = RedisModule_Call(ctx, "set", "!sc", new_key, "1");
143 RedisModule_FreeCallReply(rep);
144 RedisModule_FreeString(NULL, new_key);
145 return REDISMODULE_OK;
146}
147
148static void *KeySpace_PostNotificationsAsyncSetInner(void *arg) {
149 RedisModuleBlockedClient *bc = arg;
150 RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bc);
151 RedisModule_ThreadSafeContextLock(ctx);
152 RedisModuleCallReply* rep = RedisModule_Call(ctx, "set", "!cc", "string_x", "1");
153 RedisModule_ThreadSafeContextUnlock(ctx);
154 RedisModule_ReplyWithCallReply(ctx, rep);
155 RedisModule_FreeCallReply(rep);
156
157 RedisModule_UnblockClient(bc, NULL);
158 RedisModule_FreeThreadSafeContext(ctx);
159 return NULL;
160}
161
162static int KeySpace_PostNotificationsAsyncSet(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
163 REDISMODULE_NOT_USED(argv);
164 if (argc != 1)
165 return RedisModule_WrongArity(ctx);
166
167 pthread_t tid;
168 RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,NULL,NULL,NULL,0);
169
170 if (pthread_create(&tid,NULL,KeySpace_PostNotificationsAsyncSetInner,bc) != 0) {
171 RedisModule_AbortBlock(bc);
172 return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
173 }
174 pthread_detach(tid);
175 return REDISMODULE_OK;
176}
177
178typedef struct KeySpace_EventPostNotificationCtx {
179 RedisModuleString *triggered_on;
180 RedisModuleString *new_key;
181} KeySpace_EventPostNotificationCtx;
182
183static void KeySpace_ServerEventPostNotificationFree(void *pd) {
184 KeySpace_EventPostNotificationCtx *pn_ctx = pd;
185 RedisModule_FreeString(NULL, pn_ctx->new_key);
186 RedisModule_FreeString(NULL, pn_ctx->triggered_on);
187 RedisModule_Free(pn_ctx);
188}
189
190static void KeySpace_ServerEventPostNotification(RedisModuleCtx *ctx, void *pd) {
191 REDISMODULE_NOT_USED(ctx);
192 KeySpace_EventPostNotificationCtx *pn_ctx = pd;
193 RedisModuleCallReply* rep = RedisModule_Call(ctx, "lpush", "!ss", pn_ctx->new_key, pn_ctx->triggered_on);
194 RedisModule_FreeCallReply(rep);
195}
196
197static void KeySpace_ServerEventCallback(RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data) {
198 REDISMODULE_NOT_USED(eid);
199 REDISMODULE_NOT_USED(data);
200 if (subevent > 3) {
201 RedisModule_Log(ctx, "warning", "Got an unexpected subevent '%llu'", (unsigned long long)subevent);
202 return;
203 }
204 static const char* events[] = {
205 "before_deleted",
206 "before_expired",
207 "before_evicted",
208 "before_overwritten",
209 };
210
211 const RedisModuleString *key_name = RedisModule_GetKeyNameFromModuleKey(((RedisModuleKeyInfo*)data)->key);
212 const char *key_str = RedisModule_StringPtrLen(key_name, NULL);
213
214 for (int i = 0 ; i < 4 ; ++i) {
215 const char *event = events[i];
216 if (strncmp(key_str, event , strlen(event)) == 0) {
217 return; /* don't log any event on our tracking keys */
218 }
219 }
220
221 KeySpace_EventPostNotificationCtx *pn_ctx = RedisModule_Alloc(sizeof(*pn_ctx));
222 pn_ctx->triggered_on = RedisModule_HoldString(NULL, (RedisModuleString*)key_name);
223 pn_ctx->new_key = RedisModule_CreateString(NULL, events[subevent], strlen(events[subevent]));
224 int res = RedisModule_AddPostNotificationJob(ctx, KeySpace_ServerEventPostNotification, pn_ctx, KeySpace_ServerEventPostNotificationFree);
225 if (res == REDISMODULE_ERR) KeySpace_ServerEventPostNotificationFree(pn_ctx);
226}
227
228/* This function must be present on each Redis module. It is used in order to
229 * register the commands into the Redis server. */
230int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
231 REDISMODULE_NOT_USED(argv);
232 REDISMODULE_NOT_USED(argc);
233
234 if (RedisModule_Init(ctx,"postnotifications",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR){
235 return REDISMODULE_ERR;
236 }
237
238 if (!(RedisModule_GetModuleOptionsAll() & REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS)) {
239 return REDISMODULE_ERR;
240 }
241
242 int with_key_events = 0;
243 if (argc >= 1) {
244 const char *arg = RedisModule_StringPtrLen(argv[0], 0);
245 if (strcmp(arg, "with_key_events") == 0) {
246 with_key_events = 1;
247 }
248 }
249
250 RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS);
251
252 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_STRING, KeySpace_NotificationString) != REDISMODULE_OK){
253 return REDISMODULE_ERR;
254 }
255
256 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_STRING, KeySpace_LazyExpireInsidePostNotificationJob) != REDISMODULE_OK){
257 return REDISMODULE_ERR;
258 }
259
260 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_STRING, KeySpace_NestedNotification) != REDISMODULE_OK){
261 return REDISMODULE_ERR;
262 }
263
264 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_EXPIRED, KeySpace_NotificationExpired) != REDISMODULE_OK){
265 return REDISMODULE_ERR;
266 }
267
268 if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_EVICTED, KeySpace_NotificationEvicted) != REDISMODULE_OK){
269 return REDISMODULE_ERR;
270 }
271
272 if (with_key_events) {
273 if(RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_Key, KeySpace_ServerEventCallback) != REDISMODULE_OK){
274 return REDISMODULE_ERR;
275 }
276 }
277
278 if (RedisModule_CreateCommand(ctx, "postnotification.async_set", KeySpace_PostNotificationsAsyncSet,
279 "write", 0, 0, 0) == REDISMODULE_ERR){
280 return REDISMODULE_ERR;
281 }
282
283 return REDISMODULE_OK;
284}
285
286int RedisModule_OnUnload(RedisModuleCtx *ctx) {
287 REDISMODULE_NOT_USED(ctx);
288 return REDISMODULE_OK;
289}
diff --git a/examples/redis-unstable/tests/modules/propagate.c b/examples/redis-unstable/tests/modules/propagate.c
new file mode 100644
index 0000000..689d688
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/propagate.c
@@ -0,0 +1,403 @@
1/* This module is used to test the propagation (replication + AOF) of
2 * commands, via the RedisModule_Replicate() interface, in asynchronous
3 * contexts, such as callbacks not implementing commands, and thread safe
4 * contexts.
5 *
6 * We create a timer callback and a threads using a thread safe context.
7 * Using both we try to propagate counters increments, and later we check
8 * if the replica contains the changes as expected.
9 *
10 * -----------------------------------------------------------------------------
11 *
12 * Copyright (c) 2019-Present, Redis Ltd.
13 * All rights reserved.
14 *
15 * Licensed under your choice of (a) the Redis Source Available License 2.0
16 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
17 * GNU Affero General Public License v3 (AGPLv3).
18 */
19
20#include "redismodule.h"
21#include <pthread.h>
22#include <errno.h>
23
24#define UNUSED(V) ((void) V)
25
26RedisModuleCtx *detached_ctx = NULL;
27
28static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
29 REDISMODULE_NOT_USED(type);
30 REDISMODULE_NOT_USED(event);
31 REDISMODULE_NOT_USED(key);
32
33 RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "c!", "notifications");
34 RedisModule_FreeCallReply(rep);
35
36 return REDISMODULE_OK;
37}
38
39/* Timer callback. */
40void timerHandler(RedisModuleCtx *ctx, void *data) {
41 REDISMODULE_NOT_USED(ctx);
42 REDISMODULE_NOT_USED(data);
43
44 static int times = 0;
45
46 RedisModule_Replicate(ctx,"INCR","c","timer");
47 times++;
48
49 if (times < 3)
50 RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
51 else
52 times = 0;
53}
54
55int propagateTestTimerCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
56{
57 REDISMODULE_NOT_USED(argv);
58 REDISMODULE_NOT_USED(argc);
59
60 RedisModuleTimerID timer_id =
61 RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
62 REDISMODULE_NOT_USED(timer_id);
63
64 RedisModule_ReplyWithSimpleString(ctx,"OK");
65 return REDISMODULE_OK;
66}
67
68/* Timer callback. */
69void timerNestedHandler(RedisModuleCtx *ctx, void *data) {
70 int repl = (long long)data;
71
72 /* The goal is the trigger a module command that calls RM_Replicate
73 * in order to test MULTI/EXEC structure */
74 RedisModule_Replicate(ctx,"INCRBY","cc","timer-nested-start","1");
75 RedisModuleCallReply *reply = RedisModule_Call(ctx,"propagate-test.nested", repl? "!" : "");
76 RedisModule_FreeCallReply(reply);
77 reply = RedisModule_Call(ctx, "INCR", repl? "c!" : "c", "timer-nested-middle");
78 RedisModule_FreeCallReply(reply);
79 RedisModule_Replicate(ctx,"INCRBY","cc","timer-nested-end","1");
80}
81
82int propagateTestTimerNestedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
83{
84 REDISMODULE_NOT_USED(argv);
85 REDISMODULE_NOT_USED(argc);
86
87 RedisModuleTimerID timer_id =
88 RedisModule_CreateTimer(ctx,100,timerNestedHandler,(void*)0);
89 REDISMODULE_NOT_USED(timer_id);
90
91 RedisModule_ReplyWithSimpleString(ctx,"OK");
92 return REDISMODULE_OK;
93}
94
95int propagateTestTimerNestedReplCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
96{
97 REDISMODULE_NOT_USED(argv);
98 REDISMODULE_NOT_USED(argc);
99
100 RedisModuleTimerID timer_id =
101 RedisModule_CreateTimer(ctx,100,timerNestedHandler,(void*)1);
102 REDISMODULE_NOT_USED(timer_id);
103
104 RedisModule_ReplyWithSimpleString(ctx,"OK");
105 return REDISMODULE_OK;
106}
107
108void timerHandlerMaxmemory(RedisModuleCtx *ctx, void *data) {
109 REDISMODULE_NOT_USED(ctx);
110 REDISMODULE_NOT_USED(data);
111
112 RedisModuleCallReply *reply = RedisModule_Call(ctx,"SETEX","ccc!","timer-maxmemory-volatile-start","100","1");
113 RedisModule_FreeCallReply(reply);
114 reply = RedisModule_Call(ctx, "CONFIG", "ccc!", "SET", "maxmemory", "1");
115 RedisModule_FreeCallReply(reply);
116
117 RedisModule_Replicate(ctx, "INCR", "c", "timer-maxmemory-middle");
118
119 reply = RedisModule_Call(ctx,"SETEX","ccc!","timer-maxmemory-volatile-end","100","1");
120 RedisModule_FreeCallReply(reply);
121}
122
123int propagateTestTimerMaxmemoryCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
124{
125 REDISMODULE_NOT_USED(argv);
126 REDISMODULE_NOT_USED(argc);
127
128 RedisModuleTimerID timer_id =
129 RedisModule_CreateTimer(ctx,100,timerHandlerMaxmemory,(void*)1);
130 REDISMODULE_NOT_USED(timer_id);
131
132 RedisModule_ReplyWithSimpleString(ctx,"OK");
133 return REDISMODULE_OK;
134}
135
136void timerHandlerEval(RedisModuleCtx *ctx, void *data) {
137 REDISMODULE_NOT_USED(ctx);
138 REDISMODULE_NOT_USED(data);
139
140 RedisModuleCallReply *reply = RedisModule_Call(ctx,"INCRBY","cc!","timer-eval-start","1");
141 RedisModule_FreeCallReply(reply);
142 reply = RedisModule_Call(ctx, "EVAL", "cccc!", "redis.call('set',KEYS[1],ARGV[1])", "1", "foo", "bar");
143 RedisModule_FreeCallReply(reply);
144
145 RedisModule_Replicate(ctx, "INCR", "c", "timer-eval-middle");
146
147 reply = RedisModule_Call(ctx,"INCRBY","cc!","timer-eval-end","1");
148 RedisModule_FreeCallReply(reply);
149}
150
151int propagateTestTimerEvalCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
152{
153 REDISMODULE_NOT_USED(argv);
154 REDISMODULE_NOT_USED(argc);
155
156 RedisModuleTimerID timer_id =
157 RedisModule_CreateTimer(ctx,100,timerHandlerEval,(void*)1);
158 REDISMODULE_NOT_USED(timer_id);
159
160 RedisModule_ReplyWithSimpleString(ctx,"OK");
161 return REDISMODULE_OK;
162}
163
164/* The thread entry point. */
165void *threadMain(void *arg) {
166 REDISMODULE_NOT_USED(arg);
167 RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(NULL);
168 RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
169 for (int i = 0; i < 3; i++) {
170 RedisModule_ThreadSafeContextLock(ctx);
171 RedisModule_Replicate(ctx,"INCR","c","a-from-thread");
172 RedisModuleCallReply *reply = RedisModule_Call(ctx,"INCR","c!","thread-call");
173 RedisModule_FreeCallReply(reply);
174 RedisModule_Replicate(ctx,"INCR","c","b-from-thread");
175 RedisModule_ThreadSafeContextUnlock(ctx);
176 }
177 RedisModule_FreeThreadSafeContext(ctx);
178 return NULL;
179}
180
181int propagateTestThreadCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
182{
183 REDISMODULE_NOT_USED(argv);
184 REDISMODULE_NOT_USED(argc);
185
186 pthread_t tid;
187 if (pthread_create(&tid,NULL,threadMain,NULL) != 0)
188 return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
189 pthread_detach(tid);
190
191 RedisModule_ReplyWithSimpleString(ctx,"OK");
192 return REDISMODULE_OK;
193}
194
195/* The thread entry point. */
196void *threadDetachedMain(void *arg) {
197 REDISMODULE_NOT_USED(arg);
198 RedisModule_SelectDb(detached_ctx,9); /* Tests ran in database number 9. */
199
200 RedisModule_ThreadSafeContextLock(detached_ctx);
201 RedisModule_Replicate(detached_ctx,"INCR","c","thread-detached-before");
202 RedisModuleCallReply *reply = RedisModule_Call(detached_ctx,"INCR","c!","thread-detached-1");
203 RedisModule_FreeCallReply(reply);
204 reply = RedisModule_Call(detached_ctx,"INCR","c!","thread-detached-2");
205 RedisModule_FreeCallReply(reply);
206 RedisModule_Replicate(detached_ctx,"INCR","c","thread-detached-after");
207 RedisModule_ThreadSafeContextUnlock(detached_ctx);
208
209 return NULL;
210}
211
212int propagateTestDetachedThreadCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
213{
214 REDISMODULE_NOT_USED(argv);
215 REDISMODULE_NOT_USED(argc);
216
217 pthread_t tid;
218 if (pthread_create(&tid,NULL,threadDetachedMain,NULL) != 0)
219 return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
220 pthread_detach(tid);
221
222 RedisModule_ReplyWithSimpleString(ctx,"OK");
223 return REDISMODULE_OK;
224}
225
226int propagateTestSimpleCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
227{
228 REDISMODULE_NOT_USED(argv);
229 REDISMODULE_NOT_USED(argc);
230
231 /* Replicate two commands to test MULTI/EXEC wrapping. */
232 RedisModule_Replicate(ctx,"INCR","c","counter-1");
233 RedisModule_Replicate(ctx,"INCR","c","counter-2");
234 RedisModule_ReplyWithSimpleString(ctx,"OK");
235 return REDISMODULE_OK;
236}
237
238int propagateTestMixedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
239{
240 REDISMODULE_NOT_USED(argv);
241 REDISMODULE_NOT_USED(argc);
242 RedisModuleCallReply *reply;
243
244 /* This test mixes multiple propagation systems. */
245 reply = RedisModule_Call(ctx, "INCR", "c!", "using-call");
246 RedisModule_FreeCallReply(reply);
247
248 RedisModule_Replicate(ctx,"INCR","c","counter-1");
249 RedisModule_Replicate(ctx,"INCR","c","counter-2");
250
251 reply = RedisModule_Call(ctx, "INCR", "c!", "after-call");
252 RedisModule_FreeCallReply(reply);
253
254 RedisModule_ReplyWithSimpleString(ctx,"OK");
255 return REDISMODULE_OK;
256}
257
258int propagateTestNestedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
259{
260 REDISMODULE_NOT_USED(argv);
261 REDISMODULE_NOT_USED(argc);
262 RedisModuleCallReply *reply;
263
264 /* This test mixes multiple propagation systems. */
265 reply = RedisModule_Call(ctx, "INCR", "c!", "using-call");
266 RedisModule_FreeCallReply(reply);
267
268 reply = RedisModule_Call(ctx,"propagate-test.simple", "!");
269 RedisModule_FreeCallReply(reply);
270
271 RedisModule_Replicate(ctx,"INCR","c","counter-3");
272 RedisModule_Replicate(ctx,"INCR","c","counter-4");
273
274 reply = RedisModule_Call(ctx, "INCR", "c!", "after-call");
275 RedisModule_FreeCallReply(reply);
276
277 reply = RedisModule_Call(ctx, "INCR", "c!", "before-call-2");
278 RedisModule_FreeCallReply(reply);
279
280 reply = RedisModule_Call(ctx, "keyspace.incr_case1", "c!", "asdf"); /* Propagates INCR */
281 RedisModule_FreeCallReply(reply);
282
283 reply = RedisModule_Call(ctx, "keyspace.del_key_copy", "c!", "asdf"); /* Propagates DEL */
284 RedisModule_FreeCallReply(reply);
285
286 reply = RedisModule_Call(ctx, "INCR", "c!", "after-call-2");
287 RedisModule_FreeCallReply(reply);
288
289 RedisModule_ReplyWithSimpleString(ctx,"OK");
290 return REDISMODULE_OK;
291}
292
293int propagateTestIncr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
294{
295 REDISMODULE_NOT_USED(argc);
296 RedisModuleCallReply *reply;
297
298 /* This test propagates the module command, not the INCR it executes. */
299 reply = RedisModule_Call(ctx, "INCR", "s", argv[1]);
300 RedisModule_ReplyWithCallReply(ctx,reply);
301 RedisModule_FreeCallReply(reply);
302 RedisModule_ReplicateVerbatim(ctx);
303 return REDISMODULE_OK;
304}
305
306int propagateTestVerbatim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
307 if (argc < 2){
308 RedisModule_WrongArity(ctx);
309 return REDISMODULE_OK;
310 }
311
312 long long replicate_num;
313 RedisModule_StringToLongLong(argv[1], &replicate_num);
314 /* Replicate the command verbatim for the specified number of times. */
315 for (long long i = 0; i < replicate_num; i++)
316 RedisModule_ReplicateVerbatim(ctx);
317 RedisModule_ReplyWithSimpleString(ctx,"OK");
318 return REDISMODULE_OK;
319}
320
321int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
322 REDISMODULE_NOT_USED(argv);
323 REDISMODULE_NOT_USED(argc);
324
325 if (RedisModule_Init(ctx,"propagate-test",1,REDISMODULE_APIVER_1)
326 == REDISMODULE_ERR) return REDISMODULE_ERR;
327
328 detached_ctx = RedisModule_GetDetachedThreadSafeContext(ctx);
329
330 if (RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_ALL, KeySpace_NotificationGeneric) == REDISMODULE_ERR)
331 return REDISMODULE_ERR;
332
333 if (RedisModule_CreateCommand(ctx,"propagate-test.timer",
334 propagateTestTimerCommand,
335 "",1,1,1) == REDISMODULE_ERR)
336 return REDISMODULE_ERR;
337
338 if (RedisModule_CreateCommand(ctx,"propagate-test.timer-nested",
339 propagateTestTimerNestedCommand,
340 "",1,1,1) == REDISMODULE_ERR)
341 return REDISMODULE_ERR;
342
343 if (RedisModule_CreateCommand(ctx,"propagate-test.timer-nested-repl",
344 propagateTestTimerNestedReplCommand,
345 "",1,1,1) == REDISMODULE_ERR)
346 return REDISMODULE_ERR;
347
348 if (RedisModule_CreateCommand(ctx,"propagate-test.timer-maxmemory",
349 propagateTestTimerMaxmemoryCommand,
350 "",1,1,1) == REDISMODULE_ERR)
351 return REDISMODULE_ERR;
352
353 if (RedisModule_CreateCommand(ctx,"propagate-test.timer-eval",
354 propagateTestTimerEvalCommand,
355 "",1,1,1) == REDISMODULE_ERR)
356 return REDISMODULE_ERR;
357
358 if (RedisModule_CreateCommand(ctx,"propagate-test.thread",
359 propagateTestThreadCommand,
360 "",1,1,1) == REDISMODULE_ERR)
361 return REDISMODULE_ERR;
362
363 if (RedisModule_CreateCommand(ctx,"propagate-test.detached-thread",
364 propagateTestDetachedThreadCommand,
365 "",1,1,1) == REDISMODULE_ERR)
366 return REDISMODULE_ERR;
367
368 if (RedisModule_CreateCommand(ctx,"propagate-test.simple",
369 propagateTestSimpleCommand,
370 "",1,1,1) == REDISMODULE_ERR)
371 return REDISMODULE_ERR;
372
373 if (RedisModule_CreateCommand(ctx,"propagate-test.mixed",
374 propagateTestMixedCommand,
375 "write",1,1,1) == REDISMODULE_ERR)
376 return REDISMODULE_ERR;
377
378 if (RedisModule_CreateCommand(ctx,"propagate-test.nested",
379 propagateTestNestedCommand,
380 "write",1,1,1) == REDISMODULE_ERR)
381 return REDISMODULE_ERR;
382
383 if (RedisModule_CreateCommand(ctx,"propagate-test.incr",
384 propagateTestIncr,
385 "write",1,1,1) == REDISMODULE_ERR)
386 return REDISMODULE_ERR;
387
388 if (RedisModule_CreateCommand(ctx,"propagate-test.verbatim",
389 propagateTestVerbatim,
390 "write",1,1,1) == REDISMODULE_ERR)
391 return REDISMODULE_ERR;
392
393 return REDISMODULE_OK;
394}
395
396int RedisModule_OnUnload(RedisModuleCtx *ctx) {
397 UNUSED(ctx);
398
399 if (detached_ctx)
400 RedisModule_FreeThreadSafeContext(detached_ctx);
401
402 return REDISMODULE_OK;
403}
diff --git a/examples/redis-unstable/tests/modules/publish.c b/examples/redis-unstable/tests/modules/publish.c
new file mode 100644
index 0000000..ff276d8
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/publish.c
@@ -0,0 +1,57 @@
1#include "redismodule.h"
2#include <string.h>
3#include <assert.h>
4#include <unistd.h>
5
6#define UNUSED(V) ((void) V)
7
8int cmd_publish_classic_multi(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
9{
10 if (argc < 3)
11 return RedisModule_WrongArity(ctx);
12 RedisModule_ReplyWithArray(ctx, argc-2);
13 for (int i = 2; i < argc; i++) {
14 int receivers = RedisModule_PublishMessage(ctx, argv[1], argv[i]);
15 RedisModule_ReplyWithLongLong(ctx, receivers);
16 }
17 return REDISMODULE_OK;
18}
19
20int cmd_publish_classic(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
21{
22 if (argc != 3)
23 return RedisModule_WrongArity(ctx);
24
25 int receivers = RedisModule_PublishMessage(ctx, argv[1], argv[2]);
26 RedisModule_ReplyWithLongLong(ctx, receivers);
27 return REDISMODULE_OK;
28}
29
30int cmd_publish_shard(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
31{
32 if (argc != 3)
33 return RedisModule_WrongArity(ctx);
34
35 int receivers = RedisModule_PublishMessageShard(ctx, argv[1], argv[2]);
36 RedisModule_ReplyWithLongLong(ctx, receivers);
37 return REDISMODULE_OK;
38}
39
40int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
41 UNUSED(argv);
42 UNUSED(argc);
43
44 if (RedisModule_Init(ctx,"publish",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
45 return REDISMODULE_ERR;
46
47 if (RedisModule_CreateCommand(ctx,"publish.classic",cmd_publish_classic,"",0,0,0) == REDISMODULE_ERR)
48 return REDISMODULE_ERR;
49
50 if (RedisModule_CreateCommand(ctx,"publish.classic_multi",cmd_publish_classic_multi,"",0,0,0) == REDISMODULE_ERR)
51 return REDISMODULE_ERR;
52
53 if (RedisModule_CreateCommand(ctx,"publish.shard",cmd_publish_shard,"",0,0,0) == REDISMODULE_ERR)
54 return REDISMODULE_ERR;
55
56 return REDISMODULE_OK;
57}
diff --git a/examples/redis-unstable/tests/modules/rdbloadsave.c b/examples/redis-unstable/tests/modules/rdbloadsave.c
new file mode 100644
index 0000000..687269a
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/rdbloadsave.c
@@ -0,0 +1,162 @@
1#include "redismodule.h"
2
3#include <stdlib.h>
4#include <unistd.h>
5#include <fcntl.h>
6#include <memory.h>
7#include <errno.h>
8
9/* Sanity tests to verify inputs and return values. */
10int sanity(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
11 REDISMODULE_NOT_USED(argv);
12 REDISMODULE_NOT_USED(argc);
13
14 RedisModuleRdbStream *s = RedisModule_RdbStreamCreateFromFile("dbnew.rdb");
15
16 /* NULL stream should fail. */
17 if (RedisModule_RdbLoad(ctx, NULL, 0) == REDISMODULE_OK || errno != EINVAL) {
18 RedisModule_ReplyWithError(ctx, strerror(errno));
19 goto out;
20 }
21
22 /* Invalid flags should fail. */
23 if (RedisModule_RdbLoad(ctx, s, 188) == REDISMODULE_OK || errno != EINVAL) {
24 RedisModule_ReplyWithError(ctx, strerror(errno));
25 goto out;
26 }
27
28 /* Missing file should fail. */
29 if (RedisModule_RdbLoad(ctx, s, 0) == REDISMODULE_OK || errno != ENOENT) {
30 RedisModule_ReplyWithError(ctx, strerror(errno));
31 goto out;
32 }
33
34 /* Save RDB file. */
35 if (RedisModule_RdbSave(ctx, s, 0) != REDISMODULE_OK || errno != 0) {
36 RedisModule_ReplyWithError(ctx, strerror(errno));
37 goto out;
38 }
39
40 /* Load the saved RDB file. */
41 if (RedisModule_RdbLoad(ctx, s, 0) != REDISMODULE_OK || errno != 0) {
42 RedisModule_ReplyWithError(ctx, strerror(errno));
43 goto out;
44 }
45
46 RedisModule_ReplyWithSimpleString(ctx, "OK");
47
48 out:
49 RedisModule_RdbStreamFree(s);
50 return REDISMODULE_OK;
51}
52
53int cmd_rdbsave(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
54 if (argc != 2) {
55 RedisModule_WrongArity(ctx);
56 return REDISMODULE_OK;
57 }
58
59 size_t len;
60 const char *filename = RedisModule_StringPtrLen(argv[1], &len);
61
62 char tmp[len + 1];
63 memcpy(tmp, filename, len);
64 tmp[len] = '\0';
65
66 RedisModuleRdbStream *stream = RedisModule_RdbStreamCreateFromFile(tmp);
67
68 if (RedisModule_RdbSave(ctx, stream, 0) != REDISMODULE_OK || errno != 0) {
69 RedisModule_ReplyWithError(ctx, strerror(errno));
70 goto out;
71 }
72
73 RedisModule_ReplyWithSimpleString(ctx, "OK");
74
75out:
76 RedisModule_RdbStreamFree(stream);
77 return REDISMODULE_OK;
78}
79
80/* Fork before calling RM_RdbSave(). */
81int cmd_rdbsave_fork(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
82 if (argc != 2) {
83 RedisModule_WrongArity(ctx);
84 return REDISMODULE_OK;
85 }
86
87 size_t len;
88 const char *filename = RedisModule_StringPtrLen(argv[1], &len);
89
90 char tmp[len + 1];
91 memcpy(tmp, filename, len);
92 tmp[len] = '\0';
93
94 int fork_child_pid = RedisModule_Fork(NULL, NULL);
95 if (fork_child_pid < 0) {
96 RedisModule_ReplyWithError(ctx, strerror(errno));
97 return REDISMODULE_OK;
98 } else if (fork_child_pid > 0) {
99 /* parent */
100 RedisModule_ReplyWithSimpleString(ctx, "OK");
101 return REDISMODULE_OK;
102 }
103
104 RedisModuleRdbStream *stream = RedisModule_RdbStreamCreateFromFile(tmp);
105
106 int ret = 0;
107 if (RedisModule_RdbSave(ctx, stream, 0) != REDISMODULE_OK) {
108 ret = errno;
109 }
110 RedisModule_RdbStreamFree(stream);
111
112 RedisModule_ExitFromChild(ret);
113 return REDISMODULE_OK;
114}
115
116int cmd_rdbload(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
117 if (argc != 2) {
118 RedisModule_WrongArity(ctx);
119 return REDISMODULE_OK;
120 }
121
122 size_t len;
123 const char *filename = RedisModule_StringPtrLen(argv[1], &len);
124
125 char tmp[len + 1];
126 memcpy(tmp, filename, len);
127 tmp[len] = '\0';
128
129 RedisModuleRdbStream *stream = RedisModule_RdbStreamCreateFromFile(tmp);
130
131 if (RedisModule_RdbLoad(ctx, stream, 0) != REDISMODULE_OK || errno != 0) {
132 RedisModule_RdbStreamFree(stream);
133 RedisModule_ReplyWithError(ctx, strerror(errno));
134 return REDISMODULE_OK;
135 }
136
137 RedisModule_RdbStreamFree(stream);
138 RedisModule_ReplyWithSimpleString(ctx, "OK");
139 return REDISMODULE_OK;
140}
141
142int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
143 REDISMODULE_NOT_USED(argv);
144 REDISMODULE_NOT_USED(argc);
145
146 if (RedisModule_Init(ctx, "rdbloadsave", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
147 return REDISMODULE_ERR;
148
149 if (RedisModule_CreateCommand(ctx, "test.sanity", sanity, "", 0, 0, 0) == REDISMODULE_ERR)
150 return REDISMODULE_ERR;
151
152 if (RedisModule_CreateCommand(ctx, "test.rdbsave", cmd_rdbsave, "", 0, 0, 0) == REDISMODULE_ERR)
153 return REDISMODULE_ERR;
154
155 if (RedisModule_CreateCommand(ctx, "test.rdbsave_fork", cmd_rdbsave_fork, "", 0, 0, 0) == REDISMODULE_ERR)
156 return REDISMODULE_ERR;
157
158 if (RedisModule_CreateCommand(ctx, "test.rdbload", cmd_rdbload, "", 0, 0, 0) == REDISMODULE_ERR)
159 return REDISMODULE_ERR;
160
161 return REDISMODULE_OK;
162}
diff --git a/examples/redis-unstable/tests/modules/reply.c b/examples/redis-unstable/tests/modules/reply.c
new file mode 100644
index 0000000..c5baa66
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/reply.c
@@ -0,0 +1,214 @@
1/*
2 * A module the tests RM_ReplyWith family of commands
3 */
4
5#include "redismodule.h"
6#include <math.h>
7
8int rw_string(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
9 if (argc != 2) return RedisModule_WrongArity(ctx);
10
11 return RedisModule_ReplyWithString(ctx, argv[1]);
12}
13
14int rw_cstring(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
15 REDISMODULE_NOT_USED(argv);
16 if (argc != 1) return RedisModule_WrongArity(ctx);
17
18 return RedisModule_ReplyWithSimpleString(ctx, "A simple string");
19}
20
21int rw_int(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
22 if (argc != 2) return RedisModule_WrongArity(ctx);
23
24 long long integer;
25 if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
26 return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as an integer");
27
28 return RedisModule_ReplyWithLongLong(ctx, integer);
29}
30
31/* When one argument is given, it is returned as a double,
32 * when two arguments are given, it returns a/b. */
33int rw_double(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
34 if (argc==1)
35 return RedisModule_ReplyWithDouble(ctx, NAN);
36
37 if (argc != 2 && argc != 3) return RedisModule_WrongArity(ctx);
38
39 double dbl, dbl2;
40 if (RedisModule_StringToDouble(argv[1], &dbl) != REDISMODULE_OK)
41 return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a double");
42 if (argc == 3) {
43 if (RedisModule_StringToDouble(argv[2], &dbl2) != REDISMODULE_OK)
44 return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a double");
45 dbl /= dbl2;
46 }
47
48 return RedisModule_ReplyWithDouble(ctx, dbl);
49}
50
51int rw_longdouble(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
52 if (argc != 2) return RedisModule_WrongArity(ctx);
53
54 long double longdbl;
55 if (RedisModule_StringToLongDouble(argv[1], &longdbl) != REDISMODULE_OK)
56 return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a double");
57
58 return RedisModule_ReplyWithLongDouble(ctx, longdbl);
59}
60
61int rw_bignumber(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
62 if (argc != 2) return RedisModule_WrongArity(ctx);
63
64 size_t bignum_len;
65 const char *bignum_str = RedisModule_StringPtrLen(argv[1], &bignum_len);
66
67 return RedisModule_ReplyWithBigNumber(ctx, bignum_str, bignum_len);
68}
69
70int rw_array(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
71 if (argc != 2) return RedisModule_WrongArity(ctx);
72
73 long long integer;
74 if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
75 return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer");
76
77 RedisModule_ReplyWithArray(ctx, integer);
78 for (int i = 0; i < integer; ++i) {
79 RedisModule_ReplyWithLongLong(ctx, i);
80 }
81
82 return REDISMODULE_OK;
83}
84
85int rw_map(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
86 if (argc != 2) return RedisModule_WrongArity(ctx);
87
88 long long integer;
89 if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
90 return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer");
91
92 RedisModule_ReplyWithMap(ctx, integer);
93 for (int i = 0; i < integer; ++i) {
94 RedisModule_ReplyWithLongLong(ctx, i);
95 RedisModule_ReplyWithDouble(ctx, i * 1.5);
96 }
97
98 return REDISMODULE_OK;
99}
100
101int rw_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
102 if (argc != 2) return RedisModule_WrongArity(ctx);
103
104 long long integer;
105 if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
106 return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer");
107
108 RedisModule_ReplyWithSet(ctx, integer);
109 for (int i = 0; i < integer; ++i) {
110 RedisModule_ReplyWithLongLong(ctx, i);
111 }
112
113 return REDISMODULE_OK;
114}
115
116int rw_attribute(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
117 if (argc != 2) return RedisModule_WrongArity(ctx);
118
119 long long integer;
120 if (RedisModule_StringToLongLong(argv[1], &integer) != REDISMODULE_OK)
121 return RedisModule_ReplyWithError(ctx, "Arg cannot be parsed as a integer");
122
123 if (RedisModule_ReplyWithAttribute(ctx, integer) != REDISMODULE_OK) {
124 return RedisModule_ReplyWithError(ctx, "Attributes aren't supported by RESP 2");
125 }
126
127 for (int i = 0; i < integer; ++i) {
128 RedisModule_ReplyWithLongLong(ctx, i);
129 RedisModule_ReplyWithDouble(ctx, i * 1.5);
130 }
131
132 RedisModule_ReplyWithSimpleString(ctx, "OK");
133 return REDISMODULE_OK;
134}
135
136int rw_bool(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
137 REDISMODULE_NOT_USED(argv);
138 if (argc != 1) return RedisModule_WrongArity(ctx);
139
140 RedisModule_ReplyWithArray(ctx, 2);
141 RedisModule_ReplyWithBool(ctx, 0);
142 return RedisModule_ReplyWithBool(ctx, 1);
143}
144
145int rw_null(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
146 REDISMODULE_NOT_USED(argv);
147 if (argc != 1) return RedisModule_WrongArity(ctx);
148
149 return RedisModule_ReplyWithNull(ctx);
150}
151
152int rw_error(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
153 REDISMODULE_NOT_USED(argv);
154 if (argc != 1) return RedisModule_WrongArity(ctx);
155
156 return RedisModule_ReplyWithError(ctx, "An error");
157}
158
159int rw_error_format(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
160 if (argc != 3) return RedisModule_WrongArity(ctx);
161
162 return RedisModule_ReplyWithErrorFormat(ctx,
163 RedisModule_StringPtrLen(argv[1], NULL),
164 RedisModule_StringPtrLen(argv[2], NULL));
165}
166
167int rw_verbatim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
168 if (argc != 2) return RedisModule_WrongArity(ctx);
169
170 size_t verbatim_len;
171 const char *verbatim_str = RedisModule_StringPtrLen(argv[1], &verbatim_len);
172
173 return RedisModule_ReplyWithVerbatimString(ctx, verbatim_str, verbatim_len);
174}
175
176int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
177 REDISMODULE_NOT_USED(argv);
178 REDISMODULE_NOT_USED(argc);
179 if (RedisModule_Init(ctx, "replywith", 1, REDISMODULE_APIVER_1) != REDISMODULE_OK)
180 return REDISMODULE_ERR;
181
182 if (RedisModule_CreateCommand(ctx,"rw.string",rw_string,"",0,0,0) != REDISMODULE_OK)
183 return REDISMODULE_ERR;
184 if (RedisModule_CreateCommand(ctx,"rw.cstring",rw_cstring,"",0,0,0) != REDISMODULE_OK)
185 return REDISMODULE_ERR;
186 if (RedisModule_CreateCommand(ctx,"rw.bignumber",rw_bignumber,"",0,0,0) != REDISMODULE_OK)
187 return REDISMODULE_ERR;
188 if (RedisModule_CreateCommand(ctx,"rw.int",rw_int,"",0,0,0) != REDISMODULE_OK)
189 return REDISMODULE_ERR;
190 if (RedisModule_CreateCommand(ctx,"rw.double",rw_double,"",0,0,0) != REDISMODULE_OK)
191 return REDISMODULE_ERR;
192 if (RedisModule_CreateCommand(ctx,"rw.longdouble",rw_longdouble,"",0,0,0) != REDISMODULE_OK)
193 return REDISMODULE_ERR;
194 if (RedisModule_CreateCommand(ctx,"rw.array",rw_array,"",0,0,0) != REDISMODULE_OK)
195 return REDISMODULE_ERR;
196 if (RedisModule_CreateCommand(ctx,"rw.map",rw_map,"",0,0,0) != REDISMODULE_OK)
197 return REDISMODULE_ERR;
198 if (RedisModule_CreateCommand(ctx,"rw.attribute",rw_attribute,"",0,0,0) != REDISMODULE_OK)
199 return REDISMODULE_ERR;
200 if (RedisModule_CreateCommand(ctx,"rw.set",rw_set,"",0,0,0) != REDISMODULE_OK)
201 return REDISMODULE_ERR;
202 if (RedisModule_CreateCommand(ctx,"rw.bool",rw_bool,"",0,0,0) != REDISMODULE_OK)
203 return REDISMODULE_ERR;
204 if (RedisModule_CreateCommand(ctx,"rw.null",rw_null,"",0,0,0) != REDISMODULE_OK)
205 return REDISMODULE_ERR;
206 if (RedisModule_CreateCommand(ctx,"rw.error",rw_error,"",0,0,0) != REDISMODULE_OK)
207 return REDISMODULE_ERR;
208 if (RedisModule_CreateCommand(ctx,"rw.error_format",rw_error_format,"",0,0,0) != REDISMODULE_OK)
209 return REDISMODULE_ERR;
210 if (RedisModule_CreateCommand(ctx,"rw.verbatim",rw_verbatim,"",0,0,0) != REDISMODULE_OK)
211 return REDISMODULE_ERR;
212
213 return REDISMODULE_OK;
214}
diff --git a/examples/redis-unstable/tests/modules/scan.c b/examples/redis-unstable/tests/modules/scan.c
new file mode 100644
index 0000000..1723d30
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/scan.c
@@ -0,0 +1,121 @@
1#include "redismodule.h"
2
3#include <string.h>
4#include <assert.h>
5#include <unistd.h>
6
7typedef struct {
8 size_t nkeys;
9} scan_strings_pd;
10
11void scan_strings_callback(RedisModuleCtx *ctx, RedisModuleString* keyname, RedisModuleKey* key, void *privdata) {
12 scan_strings_pd* pd = privdata;
13 int was_opened = 0;
14 if (!key) {
15 key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ);
16 was_opened = 1;
17 }
18
19 if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_STRING) {
20 size_t len;
21 char * data = RedisModule_StringDMA(key, &len, REDISMODULE_READ);
22 RedisModule_ReplyWithArray(ctx, 2);
23 RedisModule_ReplyWithString(ctx, keyname);
24 RedisModule_ReplyWithStringBuffer(ctx, data, len);
25 pd->nkeys++;
26 }
27 if (was_opened)
28 RedisModule_CloseKey(key);
29}
30
31int scan_strings(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
32{
33 REDISMODULE_NOT_USED(argv);
34 REDISMODULE_NOT_USED(argc);
35 scan_strings_pd pd = {
36 .nkeys = 0,
37 };
38
39 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_LEN);
40
41 RedisModuleScanCursor* cursor = RedisModule_ScanCursorCreate();
42 while(RedisModule_Scan(ctx, cursor, scan_strings_callback, &pd));
43 RedisModule_ScanCursorDestroy(cursor);
44
45 RedisModule_ReplySetArrayLength(ctx, pd.nkeys);
46 return REDISMODULE_OK;
47}
48
49typedef struct {
50 RedisModuleCtx *ctx;
51 size_t nreplies;
52} scan_key_pd;
53
54void scan_key_callback(RedisModuleKey *key, RedisModuleString* field, RedisModuleString* value, void *privdata) {
55 REDISMODULE_NOT_USED(key);
56 scan_key_pd* pd = privdata;
57 RedisModule_ReplyWithArray(pd->ctx, 2);
58 size_t fieldCStrLen;
59
60 // The implementation of RedisModuleString is robj with lots of encodings.
61 // We want to make sure the robj that passes to this callback in
62 // String encoded, this is why we use RedisModule_StringPtrLen and
63 // RedisModule_ReplyWithStringBuffer instead of directly use
64 // RedisModule_ReplyWithString.
65 const char* fieldCStr = RedisModule_StringPtrLen(field, &fieldCStrLen);
66 RedisModule_ReplyWithStringBuffer(pd->ctx, fieldCStr, fieldCStrLen);
67 if(value){
68 size_t valueCStrLen;
69 const char* valueCStr = RedisModule_StringPtrLen(value, &valueCStrLen);
70 RedisModule_ReplyWithStringBuffer(pd->ctx, valueCStr, valueCStrLen);
71 } else {
72 RedisModule_ReplyWithNull(pd->ctx);
73 }
74
75 pd->nreplies++;
76}
77
78int scan_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
79{
80 if (argc != 2) {
81 RedisModule_WrongArity(ctx);
82 return REDISMODULE_OK;
83 }
84 scan_key_pd pd = {
85 .ctx = ctx,
86 .nreplies = 0,
87 };
88
89 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
90 if (!key) {
91 RedisModule_ReplyWithError(ctx, "not found");
92 return REDISMODULE_OK;
93 }
94
95 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
96
97 RedisModuleScanCursor* cursor = RedisModule_ScanCursorCreate();
98 while(RedisModule_ScanKey(key, cursor, scan_key_callback, &pd));
99 RedisModule_ScanCursorDestroy(cursor);
100
101 RedisModule_ReplySetArrayLength(ctx, pd.nreplies);
102 RedisModule_CloseKey(key);
103 return REDISMODULE_OK;
104}
105
106int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
107 REDISMODULE_NOT_USED(argv);
108 REDISMODULE_NOT_USED(argc);
109 if (RedisModule_Init(ctx, "scan", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
110 return REDISMODULE_ERR;
111
112 if (RedisModule_CreateCommand(ctx, "scan.scan_strings", scan_strings, "", 0, 0, 0) == REDISMODULE_ERR)
113 return REDISMODULE_ERR;
114
115 if (RedisModule_CreateCommand(ctx, "scan.scan_key", scan_key, "", 0, 0, 0) == REDISMODULE_ERR)
116 return REDISMODULE_ERR;
117
118 return REDISMODULE_OK;
119}
120
121
diff --git a/examples/redis-unstable/tests/modules/stream.c b/examples/redis-unstable/tests/modules/stream.c
new file mode 100644
index 0000000..65762a3
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/stream.c
@@ -0,0 +1,258 @@
1#include "redismodule.h"
2
3#include <string.h>
4#include <strings.h>
5#include <assert.h>
6#include <unistd.h>
7#include <errno.h>
8
9/* Command which adds a stream entry with automatic ID, like XADD *.
10 *
11 * Syntax: STREAM.ADD key field1 value1 [ field2 value2 ... ]
12 *
13 * The response is the ID of the added stream entry or an error message.
14 */
15int stream_add(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
16 if (argc < 2 || argc % 2 != 0) {
17 RedisModule_WrongArity(ctx);
18 return REDISMODULE_OK;
19 }
20
21 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
22 RedisModuleStreamID id;
23 if (RedisModule_StreamAdd(key, REDISMODULE_STREAM_ADD_AUTOID, &id,
24 &argv[2], (argc-2)/2) == REDISMODULE_OK) {
25 RedisModuleString *id_str = RedisModule_CreateStringFromStreamID(ctx, &id);
26 RedisModule_ReplyWithString(ctx, id_str);
27 RedisModule_FreeString(ctx, id_str);
28 } else {
29 RedisModule_ReplyWithError(ctx, "ERR StreamAdd failed");
30 }
31 RedisModule_CloseKey(key);
32 return REDISMODULE_OK;
33}
34
35/* Command which adds a stream entry N times.
36 *
37 * Syntax: STREAM.ADD key N field1 value1 [ field2 value2 ... ]
38 *
39 * Returns the number of successfully added entries.
40 */
41int stream_addn(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
42 if (argc < 3 || argc % 2 == 0) {
43 RedisModule_WrongArity(ctx);
44 return REDISMODULE_OK;
45 }
46
47 long long n, i;
48 if (RedisModule_StringToLongLong(argv[2], &n) == REDISMODULE_ERR) {
49 RedisModule_ReplyWithError(ctx, "N must be a number");
50 return REDISMODULE_OK;
51 }
52
53 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
54 for (i = 0; i < n; i++) {
55 if (RedisModule_StreamAdd(key, REDISMODULE_STREAM_ADD_AUTOID, NULL,
56 &argv[3], (argc-3)/2) == REDISMODULE_ERR)
57 break;
58 }
59 RedisModule_ReplyWithLongLong(ctx, i);
60 RedisModule_CloseKey(key);
61 return REDISMODULE_OK;
62}
63
64/* STREAM.DELETE key stream-id */
65int stream_delete(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
66 if (argc != 3) return RedisModule_WrongArity(ctx);
67 RedisModuleStreamID id;
68 if (RedisModule_StringToStreamID(argv[2], &id) != REDISMODULE_OK) {
69 return RedisModule_ReplyWithError(ctx, "Invalid stream ID");
70 }
71 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
72 if (RedisModule_StreamDelete(key, &id) == REDISMODULE_OK) {
73 RedisModule_ReplyWithSimpleString(ctx, "OK");
74 } else {
75 RedisModule_ReplyWithError(ctx, "ERR StreamDelete failed");
76 }
77 RedisModule_CloseKey(key);
78 return REDISMODULE_OK;
79}
80
81/* STREAM.RANGE key start-id end-id
82 *
83 * Returns an array of stream items. Each item is an array on the form
84 * [stream-id, [field1, value1, field2, value2, ...]].
85 *
86 * A funny side-effect used for testing RM_StreamIteratorDelete() is that if any
87 * entry has a field named "selfdestruct", the stream entry is deleted. It is
88 * however included in the results of this command.
89 */
90int stream_range(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
91 if (argc != 4) {
92 RedisModule_WrongArity(ctx);
93 return REDISMODULE_OK;
94 }
95
96 RedisModuleStreamID startid, endid;
97 if (RedisModule_StringToStreamID(argv[2], &startid) != REDISMODULE_OK ||
98 RedisModule_StringToStreamID(argv[3], &endid) != REDISMODULE_OK) {
99 RedisModule_ReplyWithError(ctx, "Invalid stream ID");
100 return REDISMODULE_OK;
101 }
102
103 /* If startid > endid, we swap and set the reverse flag. */
104 int flags = 0;
105 if (startid.ms > endid.ms ||
106 (startid.ms == endid.ms && startid.seq > endid.seq)) {
107 RedisModuleStreamID tmp = startid;
108 startid = endid;
109 endid = tmp;
110 flags |= REDISMODULE_STREAM_ITERATOR_REVERSE;
111 }
112
113 /* Open key and start iterator. */
114 int openflags = REDISMODULE_READ | REDISMODULE_WRITE;
115 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], openflags);
116 if (RedisModule_StreamIteratorStart(key, flags,
117 &startid, &endid) != REDISMODULE_OK) {
118 /* Key is not a stream, etc. */
119 RedisModule_ReplyWithError(ctx, "ERR StreamIteratorStart failed");
120 RedisModule_CloseKey(key);
121 return REDISMODULE_OK;
122 }
123
124 /* Check error handling: Delete current entry when no current entry. */
125 assert(RedisModule_StreamIteratorDelete(key) ==
126 REDISMODULE_ERR);
127 assert(errno == ENOENT);
128
129 /* Check error handling: Fetch fields when no current entry. */
130 assert(RedisModule_StreamIteratorNextField(key, NULL, NULL) ==
131 REDISMODULE_ERR);
132 assert(errno == ENOENT);
133
134 /* Return array. */
135 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_LEN);
136 RedisModule_AutoMemory(ctx);
137 RedisModuleStreamID id;
138 long numfields;
139 long len = 0;
140 while (RedisModule_StreamIteratorNextID(key, &id,
141 &numfields) == REDISMODULE_OK) {
142 RedisModule_ReplyWithArray(ctx, 2);
143 RedisModuleString *id_str = RedisModule_CreateStringFromStreamID(ctx, &id);
144 RedisModule_ReplyWithString(ctx, id_str);
145 RedisModule_ReplyWithArray(ctx, numfields * 2);
146 int delete = 0;
147 RedisModuleString *field, *value;
148 for (long i = 0; i < numfields; i++) {
149 assert(RedisModule_StreamIteratorNextField(key, &field, &value) ==
150 REDISMODULE_OK);
151 RedisModule_ReplyWithString(ctx, field);
152 RedisModule_ReplyWithString(ctx, value);
153 /* check if this is a "selfdestruct" field */
154 size_t field_len;
155 const char *field_str = RedisModule_StringPtrLen(field, &field_len);
156 if (!strncmp(field_str, "selfdestruct", field_len)) delete = 1;
157 }
158 if (delete) {
159 assert(RedisModule_StreamIteratorDelete(key) == REDISMODULE_OK);
160 }
161 /* check error handling: no more fields to fetch */
162 assert(RedisModule_StreamIteratorNextField(key, &field, &value) ==
163 REDISMODULE_ERR);
164 assert(errno == ENOENT);
165 len++;
166 }
167 RedisModule_ReplySetArrayLength(ctx, len);
168 RedisModule_StreamIteratorStop(key);
169 RedisModule_CloseKey(key);
170 return REDISMODULE_OK;
171}
172
173/*
174 * STREAM.TRIM key (MAXLEN (=|~) length | MINID (=|~) id)
175 */
176int stream_trim(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
177 if (argc != 5) {
178 RedisModule_WrongArity(ctx);
179 return REDISMODULE_OK;
180 }
181
182 /* Parse args */
183 int trim_by_id = 0; /* 0 = maxlen, 1 = minid */
184 long long maxlen;
185 RedisModuleStreamID minid;
186 size_t arg_len;
187 const char *arg = RedisModule_StringPtrLen(argv[2], &arg_len);
188 if (!strcasecmp(arg, "minid")) {
189 trim_by_id = 1;
190 if (RedisModule_StringToStreamID(argv[4], &minid) != REDISMODULE_OK) {
191 RedisModule_ReplyWithError(ctx, "ERR Invalid stream ID");
192 return REDISMODULE_OK;
193 }
194 } else if (!strcasecmp(arg, "maxlen")) {
195 if (RedisModule_StringToLongLong(argv[4], &maxlen) == REDISMODULE_ERR) {
196 RedisModule_ReplyWithError(ctx, "ERR Maxlen must be a number");
197 return REDISMODULE_OK;
198 }
199 } else {
200 RedisModule_ReplyWithError(ctx, "ERR Invalid arguments");
201 return REDISMODULE_OK;
202 }
203
204 /* Approx or exact */
205 int flags;
206 arg = RedisModule_StringPtrLen(argv[3], &arg_len);
207 if (arg_len == 1 && arg[0] == '~') {
208 flags = REDISMODULE_STREAM_TRIM_APPROX;
209 } else if (arg_len == 1 && arg[0] == '=') {
210 flags = 0;
211 } else {
212 RedisModule_ReplyWithError(ctx, "ERR Invalid approx-or-exact mark");
213 return REDISMODULE_OK;
214 }
215
216 /* Trim */
217 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
218 long long trimmed;
219 if (trim_by_id) {
220 trimmed = RedisModule_StreamTrimByID(key, flags, &minid);
221 } else {
222 trimmed = RedisModule_StreamTrimByLength(key, flags, maxlen);
223 }
224
225 /* Return result */
226 if (trimmed < 0) {
227 RedisModule_ReplyWithError(ctx, "ERR Trimming failed");
228 } else {
229 RedisModule_ReplyWithLongLong(ctx, trimmed);
230 }
231 RedisModule_CloseKey(key);
232 return REDISMODULE_OK;
233}
234
235int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
236 REDISMODULE_NOT_USED(argv);
237 REDISMODULE_NOT_USED(argc);
238 if (RedisModule_Init(ctx, "stream", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
239 return REDISMODULE_ERR;
240
241 if (RedisModule_CreateCommand(ctx, "stream.add", stream_add, "write",
242 1, 1, 1) == REDISMODULE_ERR)
243 return REDISMODULE_ERR;
244 if (RedisModule_CreateCommand(ctx, "stream.addn", stream_addn, "write",
245 1, 1, 1) == REDISMODULE_ERR)
246 return REDISMODULE_ERR;
247 if (RedisModule_CreateCommand(ctx, "stream.delete", stream_delete, "write",
248 1, 1, 1) == REDISMODULE_ERR)
249 return REDISMODULE_ERR;
250 if (RedisModule_CreateCommand(ctx, "stream.range", stream_range, "write",
251 1, 1, 1) == REDISMODULE_ERR)
252 return REDISMODULE_ERR;
253 if (RedisModule_CreateCommand(ctx, "stream.trim", stream_trim, "write",
254 1, 1, 1) == REDISMODULE_ERR)
255 return REDISMODULE_ERR;
256
257 return REDISMODULE_OK;
258}
diff --git a/examples/redis-unstable/tests/modules/subcommands.c b/examples/redis-unstable/tests/modules/subcommands.c
new file mode 100644
index 0000000..0961032
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/subcommands.c
@@ -0,0 +1,119 @@
1#include "redismodule.h"
2
3#define UNUSED(V) ((void) V)
4
5int cmd_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
6 UNUSED(argv);
7 UNUSED(argc);
8 RedisModule_ReplyWithSimpleString(ctx, "OK");
9 return REDISMODULE_OK;
10}
11
12int cmd_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
13 UNUSED(argv);
14
15 if (argc > 4) /* For testing */
16 return RedisModule_WrongArity(ctx);
17
18 RedisModule_ReplyWithSimpleString(ctx, "OK");
19 return REDISMODULE_OK;
20}
21
22int cmd_get_fullname(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
23 UNUSED(argv);
24 UNUSED(argc);
25
26 const char *command_name = RedisModule_GetCurrentCommandName(ctx);
27 RedisModule_ReplyWithSimpleString(ctx, command_name);
28 return REDISMODULE_OK;
29}
30
31int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
32 REDISMODULE_NOT_USED(argv);
33 REDISMODULE_NOT_USED(argc);
34
35 if (RedisModule_Init(ctx, "subcommands", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
36 return REDISMODULE_ERR;
37
38 /* Module command names cannot contain special characters. */
39 RedisModule_Assert(RedisModule_CreateCommand(ctx,"subcommands.char\r",NULL,"",0,0,0) == REDISMODULE_ERR);
40 RedisModule_Assert(RedisModule_CreateCommand(ctx,"subcommands.char\n",NULL,"",0,0,0) == REDISMODULE_ERR);
41 RedisModule_Assert(RedisModule_CreateCommand(ctx,"subcommands.char ",NULL,"",0,0,0) == REDISMODULE_ERR);
42
43 if (RedisModule_CreateCommand(ctx,"subcommands.bitarray",NULL,"",0,0,0) == REDISMODULE_ERR)
44 return REDISMODULE_ERR;
45 RedisModuleCommand *parent = RedisModule_GetCommand(ctx,"subcommands.bitarray");
46
47 if (RedisModule_CreateSubcommand(parent,"set",cmd_set,"",0,0,0) == REDISMODULE_ERR)
48 return REDISMODULE_ERR;
49
50 /* Module subcommand names cannot contain special characters. */
51 RedisModule_Assert(RedisModule_CreateSubcommand(parent,"char|",cmd_set,"",0,0,0) == REDISMODULE_ERR);
52 RedisModule_Assert(RedisModule_CreateSubcommand(parent,"char@",cmd_set,"",0,0,0) == REDISMODULE_ERR);
53 RedisModule_Assert(RedisModule_CreateSubcommand(parent,"char=",cmd_set,"",0,0,0) == REDISMODULE_ERR);
54
55 RedisModuleCommand *subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|set");
56 RedisModuleCommandInfo cmd_set_info = {
57 .version = REDISMODULE_COMMAND_INFO_VERSION,
58 .key_specs = (RedisModuleCommandKeySpec[]){
59 {
60 .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
61 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
62 .bs.index.pos = 1,
63 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
64 .fk.range = {0,1,0}
65 },
66 {0}
67 }
68 };
69 if (RedisModule_SetCommandInfo(subcmd, &cmd_set_info) == REDISMODULE_ERR)
70 return REDISMODULE_ERR;
71
72 if (RedisModule_CreateSubcommand(parent,"get",cmd_get,"",0,0,0) == REDISMODULE_ERR)
73 return REDISMODULE_ERR;
74 subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|get");
75 RedisModuleCommandInfo cmd_get_info = {
76 .version = REDISMODULE_COMMAND_INFO_VERSION,
77 .key_specs = (RedisModuleCommandKeySpec[]){
78 {
79 .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
80 .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
81 .bs.index.pos = 1,
82 .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
83 .fk.range = {0,1,0}
84 },
85 {0}
86 }
87 };
88 if (RedisModule_SetCommandInfo(subcmd, &cmd_get_info) == REDISMODULE_ERR)
89 return REDISMODULE_ERR;
90
91 /* Get the name of the command currently running. */
92 if (RedisModule_CreateCommand(ctx,"subcommands.parent_get_fullname",cmd_get_fullname,"",0,0,0) == REDISMODULE_ERR)
93 return REDISMODULE_ERR;
94
95 /* Get the name of the subcommand currently running. */
96 if (RedisModule_CreateCommand(ctx,"subcommands.sub",NULL,"",0,0,0) == REDISMODULE_ERR)
97 return REDISMODULE_ERR;
98
99 RedisModuleCommand *fullname_parent = RedisModule_GetCommand(ctx,"subcommands.sub");
100 if (RedisModule_CreateSubcommand(fullname_parent,"get_fullname",cmd_get_fullname,"",0,0,0) == REDISMODULE_ERR)
101 return REDISMODULE_ERR;
102
103 /* Sanity */
104
105 /* Trying to create the same subcommand fails */
106 RedisModule_Assert(RedisModule_CreateSubcommand(parent,"get",NULL,"",0,0,0) == REDISMODULE_ERR);
107
108 /* Trying to create a sub-subcommand fails */
109 RedisModule_Assert(RedisModule_CreateSubcommand(subcmd,"get",NULL,"",0,0,0) == REDISMODULE_ERR);
110
111 /* Internal container command for testing */
112 if (RedisModule_CreateCommand(ctx,"subcommands.internal_container",NULL,"internal",0,0,0) == REDISMODULE_ERR)
113 return REDISMODULE_ERR;
114 RedisModuleCommand *internal_parent = RedisModule_GetCommand(ctx,"subcommands.internal_container");
115 if (RedisModule_CreateSubcommand(internal_parent,"test",cmd_set,"internal",0,0,0) == REDISMODULE_ERR)
116 return REDISMODULE_ERR;
117
118 return REDISMODULE_OK;
119}
diff --git a/examples/redis-unstable/tests/modules/test_keymeta.c b/examples/redis-unstable/tests/modules/test_keymeta.c
new file mode 100644
index 0000000..ca5d622
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/test_keymeta.c
@@ -0,0 +1,585 @@
1/* An example module for attaching metadata to keys.
2 *
3 * This example lets tests create metadata-key classes and then SET and GET metadata
4 * to keys. The 8-byte slot stores a handle to a module-managed allocation; here
5 * we use to attach a string per-key.
6 *
7 * The module pre-registers several metadata classes during initialization and exposes
8 * the following commands (via RedisModule_CreateCommand):
9 *
10 * 1) KEYMETA.REGISTER <4-char-id> <version> [FLAGS]
11 * Register a new metadata-key class during module load.
12 * Returns the <keymeta-class-id> index (Returned from RedisModule_CreateKeyMetaClass)
13 * On failure, returns nil
14 * In a real module it should be registered "automatically" via OnLoad.
15 *
16 * FLAGS (colon-separated):
17 * KEEPONCOPY - Keep metadata on COPY operation
18 * KEEPONRENAME - Keep metadata on RENAME operation
19 * KEEPONMOVE - Keep metadata on MOVE operation
20 * UNLINKFREE - Use unlink callback for async free
21 * RDBLOAD - Enable rdb_load callback (metadata can be loaded from RDB)
22 * RDBSAVE - Enable rdb_save callback (metadata can be saved to RDB)
23 * ALLOWIGNORE - Enable ALLOW_IGNORE flag (graceful discard on load if
24 * class not registered or no rdb_load callback)
25 *
26 * Example: > keymeta.register KMT1 1 KEEPONCOPY:KEEPONRENAME:ALLOWIGNORE:RDBLOAD:RDBSAVE
27 * Example: > keymeta.register KMT2 1 ALLOWIGNORE
28 *
29 * 2) KEYMETA.SET <4-char-id> <key> <string-value>
30 * Set the string value as metadata to given key.
31 * Note:
32 * - If already set earlier, then it is expected that it will released before setting a
33 * new string. That is why this command should start with trying to get first
34 * metadata for given key.
35 *
36 * 3) KEYMETA.GET <4-char-id> <key>
37 * Get the metadata attached to the key for the given class.
38 * Returns a string attached to the given key. Or nil if nothing is attached.
39 *
40 * 4) KEYMETA.UNREGISTER <4-char-id>
41 * This will mark the key metadata class as released. It can later be reused again
42 * by the same class (consider comment above).
43 * Return REDISMODULE_OK/REDISMODULE_ERR.
44 *
45 * 5) KEYMETA.ACTIVE
46 * Return total number of active metadata at the moment.
47 */
48
49#include "redismodule.h"
50#include <string.h>
51#include <stdlib.h>
52#include <assert.h>
53
54/* Virtualize class IDs for testing. Values: 0 unused, 1..7 used, -1 released */
55RedisModuleKeyMetaClassId class_ids[8] = { 0 };
56
57/* Mapping from 4-char-id to class-id */
58typedef struct {
59 char name[5]; /* 4 chars + null terminator */
60 RedisModuleKeyMetaClassId class_id;
61} ClassMapping;
62
63#define MAX_CLASS_MAPPINGS 8
64static ClassMapping class_mappings[MAX_CLASS_MAPPINGS];
65static int num_class_mappings = 0;
66
67/* Reverse lookup: given a class_id, find the 4-char-id name */
68static const char* lookupClassName(RedisModuleKeyMetaClassId class_id) {
69 for (int i = 0; i < num_class_mappings; i++) {
70 if (class_mappings[i].class_id == class_id) {
71 return class_mappings[i].name;
72 }
73 }
74 return NULL;
75}
76
77/* Track active metadata instances (not yet freed) */
78static long long active_metadata_count = 0;
79
80/* Helper functions for class mapping */
81
82/* Add a mapping from 4-char-id to class-id */
83static int addClassMapping(const char *name, RedisModuleKeyMetaClassId class_id) {
84 if (num_class_mappings >= MAX_CLASS_MAPPINGS) {
85 return 0; /* No space */
86 }
87 strncpy(class_mappings[num_class_mappings].name, name, 4);
88 class_mappings[num_class_mappings].name[4] = '\0';
89 class_mappings[num_class_mappings].class_id = class_id;
90 num_class_mappings++;
91 return 1;
92}
93
94/* Lookup class-id by 4-char-id. Returns -1 if not found. */
95static RedisModuleKeyMetaClassId lookupClassId(const char *name) {
96 for (int i = 0; i < num_class_mappings; i++) {
97 if (strncmp(class_mappings[i].name, name, 4) == 0) {
98 return class_mappings[i].class_id;
99 }
100 }
101 return -1;
102}
103
104/* Remove a mapping by 4-char-id */
105static int removeClassMapping(const char *name) {
106 for (int i = 0; i < num_class_mappings; i++) {
107 if (strncmp(class_mappings[i].name, name, 4) == 0) {
108 /* Shift remaining entries down */
109 for (int j = i; j < num_class_mappings - 1; j++) {
110 class_mappings[j] = class_mappings[j + 1];
111 }
112 num_class_mappings--;
113 return 1;
114 }
115 }
116 return 0;
117}
118
119/* Callback functions for metadata lifecycle */
120
121/* Copy callback - called when a key is copied */
122static int KeyMetaCopyCallback(RedisModuleKeyOptCtx *ctx, uint64_t *meta) {
123 REDISMODULE_NOT_USED(ctx);
124 char *str = (char *)*meta;
125 /* Note, condition is redundant since cb only invoked when meta != reset_value */
126 if (str) {
127 char *new_str = strdup(str);
128 *meta = (uint64_t)new_str;
129 active_metadata_count++; /* New metadata instance created */
130 }
131 return 1; /* Keep metadata */
132}
133
134/* Rename callback - called when a key is renamed. */
135static int KeyMetaRenameDiscardCallback(RedisModuleKeyOptCtx *ctx, uint64_t *meta) {
136 REDISMODULE_NOT_USED(ctx);
137 REDISMODULE_NOT_USED(meta);
138 return 0;
139}
140
141/* Unlink callback - called when a key is unlinked */
142static void KeyMetaUnlinkCallback(RedisModuleKeyOptCtx *ctx, uint64_t *meta) {
143 /* Let's challenge and free early on before free callback */
144 /* Note, condition is redundant since cb only invoked when meta != reset_value */
145 if (*meta != 0) {
146 char *str = (char *)*meta;
147 free(str);
148 *meta = 0; /* Set to reset_value !!! */
149 active_metadata_count--; /* Metadata instance freed */
150 }
151 REDISMODULE_NOT_USED(ctx);
152}
153
154/* Free callback - called when metadata needs to be freed */
155static void KeyMetaFreeCallback(const char *keyname, uint64_t meta) {
156 REDISMODULE_NOT_USED(keyname);
157 /* Note, condition is redundant since cb only invoked when meta != reset_value */
158 if (meta != 0) {
159 char *str = (char *)meta;
160 free(str);
161 active_metadata_count--; /* Metadata instance freed */
162 }
163}
164
165static int KeyMetaMoveDiscardCallback(RedisModuleKeyOptCtx *ctx, uint64_t *meta) {
166 REDISMODULE_NOT_USED(ctx);
167 REDISMODULE_NOT_USED(meta);
168 return 0; /* discard metadata */
169}
170
171/* RDB Save Callback - Serialize metadata to RDB
172 * This callback is invoked during RDB save to write the metadata value.
173 *
174 * Parameters:
175 * - rdb: RedisModuleIO context for writing to RDB
176 * - value: The kvobj (key-value object) - not used in this implementation
177 * - meta: Pointer to the 8-byte metadata value (pointer to our string)
178 */
179static void KeyMetaRDBSaveCallback(RedisModuleIO *rdb, void *value, uint64_t *meta) {
180 REDISMODULE_NOT_USED(value);
181
182 /* If metadata is NULL (reset_value), don't save anything */
183 if (*meta == 0) return;
184
185 /* Extract the string from the metadata pointer */
186 char *metadata_string = (char *)*meta;
187
188 /* Save the string to RDB using SaveStringBuffer */
189 RedisModule_SaveStringBuffer(rdb, metadata_string, strlen(metadata_string));
190 /* Save more silly data */
191 RedisModule_SaveSigned(rdb, 1);
192 RedisModule_SaveFloat(rdb, 1.5);
193 RedisModule_SaveLongDouble(rdb, 0.333333333333333333L);
194}
195
196/* RDB Load Callback - Deserialize metadata from RDB
197 * This callback is invoked during RDB load to read the metadata value.
198 *
199 * Parameters:
200 * - rdb: RedisModuleIO context for reading from RDB
201 * - meta: Pointer to store the loaded 8-byte metadata value
202 * - encver: Encoding version (class version from RDB)
203 *
204 * Returns:
205 * - 1: Attach metadata to key (success)
206 * - 0: Ignore/skip metadata (not an error)
207 * - -1: Error - abort RDB load
208 */
209static int KeyMetaRDBLoadCallback(RedisModuleIO *rdb, uint64_t *meta, int encver) {
210 REDISMODULE_NOT_USED(encver);
211
212 /* Load the string from RDB using LoadStringBuffer */
213 size_t len;
214 char *loaded_string = RedisModule_LoadStringBuffer(rdb, &len);
215
216 if (loaded_string == NULL) {
217 /* Error loading string */
218 return -1;
219 }
220
221 /* Allocate and copy the string (LoadStringBuffer returns a buffer that must be freed) */
222 char *metadata_string = malloc(len + 1);
223 if (metadata_string == NULL) {
224 RedisModule_Free(loaded_string);
225 return -1;
226 }
227
228 memcpy(metadata_string, loaded_string, len);
229 metadata_string[len] = '\0';
230 RedisModule_Free(loaded_string);
231
232 /* Load the additional data that was saved (must match rdb_save) */
233 int64_t signed_val = RedisModule_LoadSigned(rdb);
234 float float_val = RedisModule_LoadFloat(rdb);
235 long double ldouble_val = RedisModule_LoadLongDouble(rdb);
236 /* We don't use these values, just need to consume them from the stream */
237 (void)signed_val;
238 (void)float_val;
239 (void)ldouble_val;
240
241 /* Store the pointer in metadata */
242 *meta = (uint64_t)metadata_string;
243 active_metadata_count++; /* New metadata instance created */
244
245 /* Return 1 to attach metadata to the key */
246 return 1;
247}
248
249/* AOF Rewrite Callback - Common implementation for all classes
250 * This callback is invoked during AOF rewrite to emit commands that will
251 * recreate the metadata when the AOF is loaded.
252 *
253 * Parameters:
254 * - aof: RedisModuleIO context for writing to AOF
255 * - value: The kvobj (key-value object) - not used in this implementation
256 * - meta: The 8-byte metadata value (pointer to our string)
257 * - class_id: The class ID for this metadata
258 */
259static void KeyMetaAOFRewriteCallback_Class(RedisModuleIO *aof, void *value, uint64_t meta, RedisModuleKeyMetaClassId class_id) {
260 REDISMODULE_NOT_USED(value);
261
262 /* If metadata is NULL (reset_value), don't emit anything */
263 if (meta == 0) return;
264
265 /* Extract the string from the metadata pointer */
266 char *metadata_string = (char *)meta;
267
268 /* Lookup the 9-byte-id name for this class */
269 const char *class_name = lookupClassName(class_id);
270 if (!class_name) {
271 /* This shouldn't happen, but handle gracefully */
272 return;
273 }
274
275 /* Get the key name from the AOF IO context */
276 const RedisModuleString *key = RedisModule_GetKeyNameFromIO(aof);
277 if (!key) {
278 /* Key name not available - shouldn't happen during AOF rewrite */
279 return;
280 }
281
282 /* Emit the KEYMETA.SET command to recreate this metadata
283 * Format: KEYMETA.SET <9-byte-id> <key> <string-value> */
284 RedisModule_EmitAOF(aof, "KEYMETA.SET", "csc",
285 class_name, /* c: 9-byte-id (C string) */
286 key, /* s: key name (RedisModuleString) */
287 metadata_string); /* c: metadata value (C string) */
288}
289
290/* Individual AOF rewrite callbacks for each class (1-7)
291 * Each callback wraps the common implementation with its specific class ID */
292static void KeyMetaAOFRewriteCb1(RedisModuleIO *aof, void *value, uint64_t meta) {
293 KeyMetaAOFRewriteCallback_Class(aof, value, meta, 1);
294}
295
296static void KeyMetaAOFRewriteCb2(RedisModuleIO *aof, void *value, uint64_t meta) {
297 KeyMetaAOFRewriteCallback_Class(aof, value, meta, 2);
298}
299
300static void KeyMetaAOFRewriteCb3(RedisModuleIO *aof, void *value, uint64_t meta) {
301 KeyMetaAOFRewriteCallback_Class(aof, value, meta, 3);
302}
303
304static void KeyMetaAOFRewriteCb4(RedisModuleIO *aof, void *value, uint64_t meta) {
305 KeyMetaAOFRewriteCallback_Class(aof, value, meta, 4);
306}
307
308static void KeyMetaAOFRewriteCb5(RedisModuleIO *aof, void *value, uint64_t meta) {
309 KeyMetaAOFRewriteCallback_Class(aof, value, meta, 5);
310}
311
312static void KeyMetaAOFRewriteCb6(RedisModuleIO *aof, void *value, uint64_t meta) {
313 KeyMetaAOFRewriteCallback_Class(aof, value, meta, 6);
314}
315
316static void KeyMetaAOFRewriteCb7(RedisModuleIO *aof, void *value, uint64_t meta) {
317 KeyMetaAOFRewriteCallback_Class(aof, value, meta, 7);
318}
319
320/* KEYMETA.REGISTER <4-char-id> <version> [KEEPONCOPY:KEEPONRENAME:UNLINKFREE:ALLOWIGNORE:NORDBLOAD:NORDBSAVE] */
321static int KeyMetaRegister_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
322 if (argc < 3 || argc > 4) {
323 return RedisModule_WrongArity(ctx);
324 }
325
326 /* argv[1]: key metadata class name */
327 size_t namelen;
328 const char *metaname = RedisModule_StringPtrLen(argv[1], &namelen);
329
330 /* argv[2]: key metadata class version */
331 long long metaver;
332 if (RedisModule_StringToLongLong(argv[2], &metaver) != REDISMODULE_OK) {
333 RedisModule_ReplyWithError(ctx, "ERR invalid version number");
334 return REDISMODULE_OK;
335 }
336
337 /* Parse optional callback flags */
338 int keep_on_copy = 0, keep_on_rename = 0, unlink_free = 0, keep_on_move = 0;
339 int allow_ignore = 0; /* Default: ALLOW_IGNORE disabled */
340 int rdb_load = 0; /* Default: rdb_load disabled */
341 int rdb_save = 0; /* Default: rdb_save disabled */
342
343 if (argc == 4) {
344 const char *flags = RedisModule_StringPtrLen(argv[3], NULL);
345 if (strstr(flags, "KEEPONCOPY")) keep_on_copy = 1;
346 if (strstr(flags, "KEEPONRENAME")) keep_on_rename = 1;
347 if (strstr(flags, "UNLINKFREE")) unlink_free = 1;
348 if (strstr(flags, "KEEPONMOVE")) keep_on_move = 1;
349 if (strstr(flags, "ALLOWIGNORE")) allow_ignore = 1; /* Enable ALLOW_IGNORE */
350 if (strstr(flags, "RDBLOAD")) rdb_load = 1; /* Enable rdb_load */
351 if (strstr(flags, "RDBSAVE")) rdb_save = 1; /* Enable rdb_save */
352 }
353
354 /* Setup configuration */
355 RedisModuleKeyMetaClassConfig config = {0};
356 config.version = REDISMODULE_KEY_META_VERSION;
357 config.flags = allow_ignore ? (1 << REDISMODULE_META_ALLOW_IGNORE) : 0;
358 config.reset_value = (uint64_t)NULL; /* NULL pointer means no resource to free */
359 config.rdb_load = rdb_load ? KeyMetaRDBLoadCallback : NULL;
360 config.rdb_save = rdb_save ? KeyMetaRDBSaveCallback : NULL;
361 switch (num_class_mappings + 1) { /* distinct cb per class */
362 case 1: config.aof_rewrite = KeyMetaAOFRewriteCb1; break;
363 case 2: config.aof_rewrite = KeyMetaAOFRewriteCb2; break;
364 case 3: config.aof_rewrite = KeyMetaAOFRewriteCb3; break;
365 case 4: config.aof_rewrite = KeyMetaAOFRewriteCb4; break;
366 case 5: config.aof_rewrite = KeyMetaAOFRewriteCb5; break;
367 case 6: config.aof_rewrite = KeyMetaAOFRewriteCb6; break;
368 case 7: config.aof_rewrite = KeyMetaAOFRewriteCb7; break;
369 default: config.aof_rewrite = NULL; break;
370 }
371 config.free = KeyMetaFreeCallback;
372 config.copy = keep_on_copy ? KeyMetaCopyCallback : NULL;
373 config.rename = keep_on_rename ? NULL : KeyMetaRenameDiscardCallback;
374 config.move = keep_on_move ? NULL : KeyMetaMoveDiscardCallback;
375 config.defrag = NULL;
376 config.unlink = unlink_free ? KeyMetaUnlinkCallback : NULL;
377 config.mem_usage = NULL;
378 config.free_effort = NULL;
379
380 /* Create the metadata class */
381 RedisModuleKeyMetaClassId class_id = RedisModule_CreateKeyMetaClass(ctx, metaname, (int)metaver, &config);
382
383 if (class_id < 0) {
384 RedisModule_ReplyWithError(ctx, "ERR failed to create metadata class");
385 return REDISMODULE_OK;
386 } else {
387 /* Store the mapping from 9-byte-id to class-id */
388 if (!addClassMapping(metaname, class_id)) {
389 RedisModule_ReplyWithError(ctx, "ERR failed to store class mapping");
390 return REDISMODULE_OK;
391 }
392 RedisModule_ReplyWithLongLong(ctx, class_id);
393 }
394
395 return REDISMODULE_OK;
396}
397
398/* KEYMETA.SET <9-byte-id> <key> <string-value> */
399static int KeyMetaSet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
400 if (argc != 4) {
401 return RedisModule_WrongArity(ctx);
402 }
403
404 /* Parse arguments */
405 const char *metaname = RedisModule_StringPtrLen(argv[1], NULL);
406 RedisModuleString *keyname = argv[2];
407 const char *value = RedisModule_StringPtrLen(argv[3], NULL);
408
409 /* Lookup the metadata class by name */
410 RedisModuleKeyMetaClassId class_id = lookupClassId(metaname);
411 if (class_id < 0) {
412 RedisModule_ReplyWithError(ctx, "ERR metadata class not found");
413 return REDISMODULE_OK;
414 }
415
416 /* Open the key for writing */
417 RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ | REDISMODULE_WRITE);
418
419 if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
420 RedisModule_ReplyWithNull(ctx);
421 RedisModule_CloseKey(key);
422 return REDISMODULE_OK;
423 }
424
425 /* Check if metadata already exists and free it first.
426 *
427 * Note: The caller is responsible for retrieving and freeing any existing
428 * pointer-based metadata before RM_SetKeyMeta() to a new value
429 */
430 uint64_t meta = 0;
431 if (RedisModule_GetKeyMeta(class_id, key, &meta) == REDISMODULE_OK) {
432 if (meta != 0) {
433 free((char *)meta);
434 active_metadata_count--; /* Old metadata freed */
435 }
436 }
437
438 char *new_str = strdup(value);
439 int res = RedisModule_SetKeyMeta(class_id, key, (uint64_t)new_str);
440
441 if (res == REDISMODULE_OK) {
442 active_metadata_count++; /* New metadata instance created */
443 }
444
445 RedisModule_CloseKey(key);
446
447 if (res == REDISMODULE_OK) {
448 RedisModule_ReplyWithSimpleString(ctx, "OK");
449 } else {
450 free(new_str);
451 RedisModule_ReplyWithError(ctx, "ERR failed to set metadata");
452 }
453 return REDISMODULE_OK;
454}
455
456/* KEYMETA.GET <9-byte-id> <key> */
457static int KeyMetaGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
458 if (argc != 3) {
459 return RedisModule_WrongArity(ctx);
460 }
461
462 /* Parse arguments */
463 const char *metaname = RedisModule_StringPtrLen(argv[1], NULL);
464 RedisModuleString *keyname = argv[2];
465
466 /* Lookup the metadata class by name */
467 RedisModuleKeyMetaClassId class_id = lookupClassId(metaname);
468 if (class_id < 0) {
469 RedisModule_ReplyWithError(ctx, "ERR metadata class not found");
470 return REDISMODULE_OK;
471 }
472
473 /* Open the key for reading */
474 RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ);
475 if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
476 RedisModule_ReplyWithNull(ctx);
477 RedisModule_CloseKey(key);
478 return REDISMODULE_OK;
479 }
480
481 /* Get the metadata */
482 uint64_t meta = 0;
483 int result = RedisModule_GetKeyMeta(class_id, key, &meta);
484
485 RedisModule_CloseKey(key);
486
487 if (result == REDISMODULE_OK && meta != 0) {
488 char *str = (char *)meta;
489 RedisModule_ReplyWithCString(ctx, str);
490 } else {
491 RedisModule_ReplyWithNull(ctx);
492 }
493
494 return REDISMODULE_OK;
495}
496
497/* KEYMETA.UNREGISTER <9-byte-id> */
498static int KeyMetaUnregister_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
499 if (argc != 2) {
500 return RedisModule_WrongArity(ctx);
501 }
502
503 /* Parse arguments */
504 const char *metaname = RedisModule_StringPtrLen(argv[1], NULL);
505
506 /* Lookup the metadata class by name */
507 RedisModuleKeyMetaClassId class_id = lookupClassId(metaname);
508 if (class_id < 0) {
509 RedisModule_ReplyWithError(ctx, "ERR metadata class not found");
510 return REDISMODULE_OK;
511 }
512
513 /* Release the metadata class */
514 int result = RedisModule_ReleaseKeyMetaClass(class_id);
515
516 if (result == REDISMODULE_OK) {
517 /* Remove the mapping */
518 removeClassMapping(metaname);
519 RedisModule_ReplyWithSimpleString(ctx, "OK");
520 } else {
521 RedisModule_ReplyWithError(ctx, "ERR failed to unregister class");
522 }
523 return REDISMODULE_OK;
524}
525
526/* KEYMETA.ACTIVE
527 * Returns the total number of active metadata instances that haven't been freed yet.
528 * This is useful for testing to verify that metadata is properly cleaned up. */
529static int KeyMetaActive_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
530 if (argc != 1) {
531 return RedisModule_WrongArity(ctx);
532 }
533 REDISMODULE_NOT_USED(argv);
534
535 RedisModule_ReplyWithLongLong(ctx, active_metadata_count);
536 return REDISMODULE_OK;
537}
538
539/* Module initialization */
540int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
541 REDISMODULE_NOT_USED(argv);
542 REDISMODULE_NOT_USED(argc);
543
544 if (RedisModule_Init(ctx, "test_metakey", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
545 return REDISMODULE_ERR;
546 }
547
548 /* Register commands */
549 if (RedisModule_CreateCommand(ctx, "keymeta.register",
550 KeyMetaRegister_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR) {
551 return REDISMODULE_ERR;
552 }
553
554 if (RedisModule_CreateCommand(ctx, "keymeta.set",
555 KeyMetaSet_RedisCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) {
556 return REDISMODULE_ERR;
557 }
558
559 if (RedisModule_CreateCommand(ctx, "keymeta.get",
560 KeyMetaGet_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) {
561 return REDISMODULE_ERR;
562 }
563
564 if (RedisModule_CreateCommand(ctx, "keymeta.unregister",
565 KeyMetaUnregister_RedisCommand, "write", 0, 0, 0) == REDISMODULE_ERR) {
566 return REDISMODULE_ERR;
567 }
568
569 if (RedisModule_CreateCommand(ctx, "keymeta.active",
570 KeyMetaActive_RedisCommand, "readonly fast", 0, 0, 0) == REDISMODULE_ERR) {
571 return REDISMODULE_ERR;
572 }
573
574 return REDISMODULE_OK;
575}
576
577int RedisModule_OnUnload(RedisModuleCtx *ctx) {
578 REDISMODULE_NOT_USED(ctx);
579 long unsigned int i;
580 for (i = 0 ; i < sizeof(class_ids) / sizeof(class_ids[0]); i++) {
581 if (class_ids[i] > 0)
582 RedisModule_ReleaseKeyMetaClass(class_ids[i]);
583 }
584 return REDISMODULE_OK;
585}
diff --git a/examples/redis-unstable/tests/modules/test_lazyfree.c b/examples/redis-unstable/tests/modules/test_lazyfree.c
new file mode 100644
index 0000000..7ba213f
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/test_lazyfree.c
@@ -0,0 +1,196 @@
1/* This module emulates a linked list for lazyfree testing of modules, which
2 is a simplified version of 'hellotype.c'
3 */
4#include "redismodule.h"
5#include <stdio.h>
6#include <stdlib.h>
7#include <ctype.h>
8#include <string.h>
9#include <stdint.h>
10
11static RedisModuleType *LazyFreeLinkType;
12
13struct LazyFreeLinkNode {
14 int64_t value;
15 struct LazyFreeLinkNode *next;
16};
17
18struct LazyFreeLinkObject {
19 struct LazyFreeLinkNode *head;
20 size_t len; /* Number of elements added. */
21};
22
23struct LazyFreeLinkObject *createLazyFreeLinkObject(void) {
24 struct LazyFreeLinkObject *o;
25 o = RedisModule_Alloc(sizeof(*o));
26 o->head = NULL;
27 o->len = 0;
28 return o;
29}
30
31void LazyFreeLinkInsert(struct LazyFreeLinkObject *o, int64_t ele) {
32 struct LazyFreeLinkNode *next = o->head, *newnode, *prev = NULL;
33
34 while(next && next->value < ele) {
35 prev = next;
36 next = next->next;
37 }
38 newnode = RedisModule_Alloc(sizeof(*newnode));
39 newnode->value = ele;
40 newnode->next = next;
41 if (prev) {
42 prev->next = newnode;
43 } else {
44 o->head = newnode;
45 }
46 o->len++;
47}
48
49void LazyFreeLinkReleaseObject(struct LazyFreeLinkObject *o) {
50 struct LazyFreeLinkNode *cur, *next;
51 cur = o->head;
52 while(cur) {
53 next = cur->next;
54 RedisModule_Free(cur);
55 cur = next;
56 }
57 RedisModule_Free(o);
58}
59
60/* LAZYFREELINK.INSERT key value */
61int LazyFreeLinkInsert_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
62 RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
63
64 if (argc != 3) return RedisModule_WrongArity(ctx);
65 RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
66 REDISMODULE_READ|REDISMODULE_WRITE);
67 int type = RedisModule_KeyType(key);
68 if (type != REDISMODULE_KEYTYPE_EMPTY &&
69 RedisModule_ModuleTypeGetType(key) != LazyFreeLinkType)
70 {
71 return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
72 }
73
74 long long value;
75 if ((RedisModule_StringToLongLong(argv[2],&value) != REDISMODULE_OK)) {
76 return RedisModule_ReplyWithError(ctx,"ERR invalid value: must be a signed 64 bit integer");
77 }
78
79 struct LazyFreeLinkObject *hto;
80 if (type == REDISMODULE_KEYTYPE_EMPTY) {
81 hto = createLazyFreeLinkObject();
82 RedisModule_ModuleTypeSetValue(key,LazyFreeLinkType,hto);
83 } else {
84 hto = RedisModule_ModuleTypeGetValue(key);
85 }
86
87 LazyFreeLinkInsert(hto,value);
88 RedisModule_SignalKeyAsReady(ctx,argv[1]);
89
90 RedisModule_ReplyWithLongLong(ctx,hto->len);
91 RedisModule_ReplicateVerbatim(ctx);
92 return REDISMODULE_OK;
93}
94
95/* LAZYFREELINK.LEN key */
96int LazyFreeLinkLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
97 RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
98
99 if (argc != 2) return RedisModule_WrongArity(ctx);
100 RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
101 REDISMODULE_READ);
102 int type = RedisModule_KeyType(key);
103 if (type != REDISMODULE_KEYTYPE_EMPTY &&
104 RedisModule_ModuleTypeGetType(key) != LazyFreeLinkType)
105 {
106 return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
107 }
108
109 struct LazyFreeLinkObject *hto = RedisModule_ModuleTypeGetValue(key);
110 RedisModule_ReplyWithLongLong(ctx,hto ? hto->len : 0);
111 return REDISMODULE_OK;
112}
113
114void *LazyFreeLinkRdbLoad(RedisModuleIO *rdb, int encver) {
115 if (encver != 0) {
116 return NULL;
117 }
118 uint64_t elements = RedisModule_LoadUnsigned(rdb);
119 struct LazyFreeLinkObject *hto = createLazyFreeLinkObject();
120 while(elements--) {
121 int64_t ele = RedisModule_LoadSigned(rdb);
122 LazyFreeLinkInsert(hto,ele);
123 }
124 return hto;
125}
126
127void LazyFreeLinkRdbSave(RedisModuleIO *rdb, void *value) {
128 struct LazyFreeLinkObject *hto = value;
129 struct LazyFreeLinkNode *node = hto->head;
130 RedisModule_SaveUnsigned(rdb,hto->len);
131 while(node) {
132 RedisModule_SaveSigned(rdb,node->value);
133 node = node->next;
134 }
135}
136
137void LazyFreeLinkAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) {
138 struct LazyFreeLinkObject *hto = value;
139 struct LazyFreeLinkNode *node = hto->head;
140 while(node) {
141 RedisModule_EmitAOF(aof,"LAZYFREELINK.INSERT","sl",key,node->value);
142 node = node->next;
143 }
144}
145
146void LazyFreeLinkFree(void *value) {
147 LazyFreeLinkReleaseObject(value);
148}
149
150size_t LazyFreeLinkFreeEffort(RedisModuleString *key, const void *value) {
151 REDISMODULE_NOT_USED(key);
152 const struct LazyFreeLinkObject *hto = value;
153 return hto->len;
154}
155
156void LazyFreeLinkUnlink(RedisModuleString *key, const void *value) {
157 REDISMODULE_NOT_USED(key);
158 REDISMODULE_NOT_USED(value);
159 /* Here you can know which key and value is about to be freed. */
160}
161
162int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
163 REDISMODULE_NOT_USED(argv);
164 REDISMODULE_NOT_USED(argc);
165
166 if (RedisModule_Init(ctx,"lazyfreetest",1,REDISMODULE_APIVER_1)
167 == REDISMODULE_ERR) return REDISMODULE_ERR;
168
169 /* We only allow our module to be loaded when the redis core version is greater than the version of my module */
170 if (RedisModule_GetTypeMethodVersion() < REDISMODULE_TYPE_METHOD_VERSION) {
171 return REDISMODULE_ERR;
172 }
173
174 RedisModuleTypeMethods tm = {
175 .version = REDISMODULE_TYPE_METHOD_VERSION,
176 .rdb_load = LazyFreeLinkRdbLoad,
177 .rdb_save = LazyFreeLinkRdbSave,
178 .aof_rewrite = LazyFreeLinkAofRewrite,
179 .free = LazyFreeLinkFree,
180 .free_effort = LazyFreeLinkFreeEffort,
181 .unlink = LazyFreeLinkUnlink,
182 };
183
184 LazyFreeLinkType = RedisModule_CreateDataType(ctx,"test_lazy",0,&tm);
185 if (LazyFreeLinkType == NULL) return REDISMODULE_ERR;
186
187 if (RedisModule_CreateCommand(ctx,"lazyfreelink.insert",
188 LazyFreeLinkInsert_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
189 return REDISMODULE_ERR;
190
191 if (RedisModule_CreateCommand(ctx,"lazyfreelink.len",
192 LazyFreeLinkLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
193 return REDISMODULE_ERR;
194
195 return REDISMODULE_OK;
196}
diff --git a/examples/redis-unstable/tests/modules/testrdb.c b/examples/redis-unstable/tests/modules/testrdb.c
new file mode 100644
index 0000000..c31aebb
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/testrdb.c
@@ -0,0 +1,405 @@
1#include "redismodule.h"
2
3#include <string.h>
4#include <assert.h>
5
6/* Module configuration, save aux or not? */
7#define CONF_AUX_OPTION_NO_AUX 0
8#define CONF_AUX_OPTION_SAVE2 1 << 0
9#define CONF_AUX_OPTION_BEFORE_KEYSPACE 1 << 1
10#define CONF_AUX_OPTION_AFTER_KEYSPACE 1 << 2
11#define CONF_AUX_OPTION_NO_DATA 1 << 3
12long long conf_aux_count = 0;
13
14/* Registered type */
15RedisModuleType *testrdb_type = NULL;
16
17/* Global values to store and persist to aux */
18RedisModuleString *before_str = NULL;
19RedisModuleString *after_str = NULL;
20
21/* Global values used to keep aux from db being loaded (in case of async_loading) */
22RedisModuleString *before_str_temp = NULL;
23RedisModuleString *after_str_temp = NULL;
24
25/* Indicates whether there is an async replication in progress.
26 * We control this value from RedisModuleEvent_ReplAsyncLoad events. */
27int async_loading = 0;
28
29int n_aux_load_called = 0;
30
31void replAsyncLoadCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
32{
33 REDISMODULE_NOT_USED(e);
34 REDISMODULE_NOT_USED(data);
35
36 switch (sub) {
37 case REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED:
38 assert(async_loading == 0);
39 async_loading = 1;
40 break;
41 case REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_ABORTED:
42 /* Discard temp aux */
43 if (before_str_temp)
44 RedisModule_FreeString(ctx, before_str_temp);
45 if (after_str_temp)
46 RedisModule_FreeString(ctx, after_str_temp);
47 before_str_temp = NULL;
48 after_str_temp = NULL;
49
50 async_loading = 0;
51 break;
52 case REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_COMPLETED:
53 if (before_str)
54 RedisModule_FreeString(ctx, before_str);
55 if (after_str)
56 RedisModule_FreeString(ctx, after_str);
57 before_str = before_str_temp;
58 after_str = after_str_temp;
59
60 before_str_temp = NULL;
61 after_str_temp = NULL;
62
63 async_loading = 0;
64 break;
65 default:
66 assert(0);
67 }
68}
69
70void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
71 int count = RedisModule_LoadSigned(rdb);
72 RedisModuleString *str = RedisModule_LoadString(rdb);
73 float f = RedisModule_LoadFloat(rdb);
74 long double ld = RedisModule_LoadLongDouble(rdb);
75 if (RedisModule_IsIOError(rdb)) {
76 RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
77 if (str)
78 RedisModule_FreeString(ctx, str);
79 return NULL;
80 }
81 /* Using the values only after checking for io errors. */
82 assert(count==1);
83 assert(encver==1);
84 assert(f==1.5f);
85 assert(ld==0.333333333333333333L);
86 return str;
87}
88
89void testrdb_type_save(RedisModuleIO *rdb, void *value) {
90 RedisModuleString *str = (RedisModuleString*)value;
91 RedisModule_SaveSigned(rdb, 1);
92 RedisModule_SaveString(rdb, str);
93 RedisModule_SaveFloat(rdb, 1.5);
94 RedisModule_SaveLongDouble(rdb, 0.333333333333333333L);
95}
96
97void testrdb_aux_save(RedisModuleIO *rdb, int when) {
98 if (!(conf_aux_count & CONF_AUX_OPTION_BEFORE_KEYSPACE)) assert(when == REDISMODULE_AUX_AFTER_RDB);
99 if (!(conf_aux_count & CONF_AUX_OPTION_AFTER_KEYSPACE)) assert(when == REDISMODULE_AUX_BEFORE_RDB);
100 assert(conf_aux_count!=CONF_AUX_OPTION_NO_AUX);
101 if (when == REDISMODULE_AUX_BEFORE_RDB) {
102 if (before_str) {
103 RedisModule_SaveSigned(rdb, 1);
104 RedisModule_SaveString(rdb, before_str);
105 } else {
106 RedisModule_SaveSigned(rdb, 0);
107 }
108 } else {
109 if (after_str) {
110 RedisModule_SaveSigned(rdb, 1);
111 RedisModule_SaveString(rdb, after_str);
112 } else {
113 RedisModule_SaveSigned(rdb, 0);
114 }
115 }
116}
117
118int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
119 assert(encver == 1);
120 if (!(conf_aux_count & CONF_AUX_OPTION_BEFORE_KEYSPACE)) assert(when == REDISMODULE_AUX_AFTER_RDB);
121 if (!(conf_aux_count & CONF_AUX_OPTION_AFTER_KEYSPACE)) assert(when == REDISMODULE_AUX_BEFORE_RDB);
122 assert(conf_aux_count!=CONF_AUX_OPTION_NO_AUX);
123 RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
124 if (when == REDISMODULE_AUX_BEFORE_RDB) {
125 if (async_loading == 0) {
126 if (before_str)
127 RedisModule_FreeString(ctx, before_str);
128 before_str = NULL;
129 int count = RedisModule_LoadSigned(rdb);
130 if (RedisModule_IsIOError(rdb))
131 return REDISMODULE_ERR;
132 if (count)
133 before_str = RedisModule_LoadString(rdb);
134 } else {
135 if (before_str_temp)
136 RedisModule_FreeString(ctx, before_str_temp);
137 before_str_temp = NULL;
138 int count = RedisModule_LoadSigned(rdb);
139 if (RedisModule_IsIOError(rdb))
140 return REDISMODULE_ERR;
141 if (count)
142 before_str_temp = RedisModule_LoadString(rdb);
143 }
144 } else {
145 if (async_loading == 0) {
146 if (after_str)
147 RedisModule_FreeString(ctx, after_str);
148 after_str = NULL;
149 int count = RedisModule_LoadSigned(rdb);
150 if (RedisModule_IsIOError(rdb))
151 return REDISMODULE_ERR;
152 if (count)
153 after_str = RedisModule_LoadString(rdb);
154 } else {
155 if (after_str_temp)
156 RedisModule_FreeString(ctx, after_str_temp);
157 after_str_temp = NULL;
158 int count = RedisModule_LoadSigned(rdb);
159 if (RedisModule_IsIOError(rdb))
160 return REDISMODULE_ERR;
161 if (count)
162 after_str_temp = RedisModule_LoadString(rdb);
163 }
164 }
165
166 if (RedisModule_IsIOError(rdb))
167 return REDISMODULE_ERR;
168 return REDISMODULE_OK;
169}
170
171void testrdb_type_free(void *value) {
172 if (value)
173 RedisModule_FreeString(NULL, (RedisModuleString*)value);
174}
175
176int testrdb_set_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
177{
178 if (argc != 2) {
179 RedisModule_WrongArity(ctx);
180 return REDISMODULE_OK;
181 }
182
183 if (before_str)
184 RedisModule_FreeString(ctx, before_str);
185 before_str = argv[1];
186 RedisModule_RetainString(ctx, argv[1]);
187 RedisModule_ReplyWithLongLong(ctx, 1);
188 return REDISMODULE_OK;
189}
190
191int testrdb_get_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
192{
193 REDISMODULE_NOT_USED(argv);
194 if (argc != 1){
195 RedisModule_WrongArity(ctx);
196 return REDISMODULE_OK;
197 }
198 if (before_str)
199 RedisModule_ReplyWithString(ctx, before_str);
200 else
201 RedisModule_ReplyWithStringBuffer(ctx, "", 0);
202 return REDISMODULE_OK;
203}
204
205/* For purpose of testing module events, expose variable state during async_loading. */
206int testrdb_async_loading_get_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
207{
208 REDISMODULE_NOT_USED(argv);
209 if (argc != 1){
210 RedisModule_WrongArity(ctx);
211 return REDISMODULE_OK;
212 }
213 if (before_str_temp)
214 RedisModule_ReplyWithString(ctx, before_str_temp);
215 else
216 RedisModule_ReplyWithStringBuffer(ctx, "", 0);
217 return REDISMODULE_OK;
218}
219
220int testrdb_set_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
221{
222 if (argc != 2){
223 RedisModule_WrongArity(ctx);
224 return REDISMODULE_OK;
225 }
226
227 if (after_str)
228 RedisModule_FreeString(ctx, after_str);
229 after_str = argv[1];
230 RedisModule_RetainString(ctx, argv[1]);
231 RedisModule_ReplyWithLongLong(ctx, 1);
232 return REDISMODULE_OK;
233}
234
235int testrdb_get_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
236{
237 REDISMODULE_NOT_USED(argv);
238 if (argc != 1){
239 RedisModule_WrongArity(ctx);
240 return REDISMODULE_OK;
241 }
242 if (after_str)
243 RedisModule_ReplyWithString(ctx, after_str);
244 else
245 RedisModule_ReplyWithStringBuffer(ctx, "", 0);
246 return REDISMODULE_OK;
247}
248
249int testrdb_set_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
250{
251 if (argc != 3){
252 RedisModule_WrongArity(ctx);
253 return REDISMODULE_OK;
254 }
255
256 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
257 RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
258 if (str)
259 RedisModule_FreeString(ctx, str);
260 RedisModule_ModuleTypeSetValue(key, testrdb_type, argv[2]);
261 RedisModule_RetainString(ctx, argv[2]);
262 RedisModule_CloseKey(key);
263 RedisModule_ReplyWithLongLong(ctx, 1);
264 return REDISMODULE_OK;
265}
266
267int testrdb_get_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
268{
269 if (argc != 2){
270 RedisModule_WrongArity(ctx);
271 return REDISMODULE_OK;
272 }
273
274 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
275 RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
276 RedisModule_CloseKey(key);
277 RedisModule_ReplyWithString(ctx, str);
278 return REDISMODULE_OK;
279}
280
281int testrdb_get_n_aux_load_called(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
282{
283 REDISMODULE_NOT_USED(ctx);
284 REDISMODULE_NOT_USED(argv);
285 REDISMODULE_NOT_USED(argc);
286 RedisModule_ReplyWithLongLong(ctx, n_aux_load_called);
287 return REDISMODULE_OK;
288}
289
290int test2rdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
291 REDISMODULE_NOT_USED(rdb);
292 REDISMODULE_NOT_USED(encver);
293 REDISMODULE_NOT_USED(when);
294 n_aux_load_called++;
295 return REDISMODULE_OK;
296}
297
298void test2rdb_aux_save(RedisModuleIO *rdb, int when) {
299 REDISMODULE_NOT_USED(rdb);
300 REDISMODULE_NOT_USED(when);
301}
302
303int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
304 REDISMODULE_NOT_USED(argv);
305 REDISMODULE_NOT_USED(argc);
306 if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
307 return REDISMODULE_ERR;
308
309 RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS | REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD);
310
311 if (argc > 0)
312 RedisModule_StringToLongLong(argv[0], &conf_aux_count);
313
314 if (conf_aux_count==CONF_AUX_OPTION_NO_AUX) {
315 RedisModuleTypeMethods datatype_methods = {
316 .version = 1,
317 .rdb_load = testrdb_type_load,
318 .rdb_save = testrdb_type_save,
319 .aof_rewrite = NULL,
320 .digest = NULL,
321 .free = testrdb_type_free,
322 };
323
324 testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
325 if (testrdb_type == NULL)
326 return REDISMODULE_ERR;
327 } else if (!(conf_aux_count & CONF_AUX_OPTION_NO_DATA)) {
328 RedisModuleTypeMethods datatype_methods = {
329 .version = REDISMODULE_TYPE_METHOD_VERSION,
330 .rdb_load = testrdb_type_load,
331 .rdb_save = testrdb_type_save,
332 .aof_rewrite = NULL,
333 .digest = NULL,
334 .free = testrdb_type_free,
335 .aux_load = testrdb_aux_load,
336 .aux_save = testrdb_aux_save,
337 .aux_save_triggers = ((conf_aux_count & CONF_AUX_OPTION_BEFORE_KEYSPACE) ? REDISMODULE_AUX_BEFORE_RDB : 0) |
338 ((conf_aux_count & CONF_AUX_OPTION_AFTER_KEYSPACE) ? REDISMODULE_AUX_AFTER_RDB : 0)
339 };
340
341 if (conf_aux_count & CONF_AUX_OPTION_SAVE2) {
342 datatype_methods.aux_save2 = testrdb_aux_save;
343 }
344
345 testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
346 if (testrdb_type == NULL)
347 return REDISMODULE_ERR;
348 } else {
349
350 /* Used to verify that aux_save2 api without any data, saves nothing to the RDB. */
351 RedisModuleTypeMethods datatype_methods = {
352 .version = REDISMODULE_TYPE_METHOD_VERSION,
353 .aux_load = test2rdb_aux_load,
354 .aux_save = test2rdb_aux_save,
355 .aux_save_triggers = ((conf_aux_count & CONF_AUX_OPTION_BEFORE_KEYSPACE) ? REDISMODULE_AUX_BEFORE_RDB : 0) |
356 ((conf_aux_count & CONF_AUX_OPTION_AFTER_KEYSPACE) ? REDISMODULE_AUX_AFTER_RDB : 0)
357 };
358 if (conf_aux_count & CONF_AUX_OPTION_SAVE2) {
359 datatype_methods.aux_save2 = test2rdb_aux_save;
360 }
361
362 RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
363 }
364
365 if (RedisModule_CreateCommand(ctx,"testrdb.set.before", testrdb_set_before,"deny-oom",0,0,0) == REDISMODULE_ERR)
366 return REDISMODULE_ERR;
367
368 if (RedisModule_CreateCommand(ctx,"testrdb.get.before", testrdb_get_before,"",0,0,0) == REDISMODULE_ERR)
369 return REDISMODULE_ERR;
370
371 if (RedisModule_CreateCommand(ctx,"testrdb.async_loading.get.before", testrdb_async_loading_get_before,"",0,0,0) == REDISMODULE_ERR)
372 return REDISMODULE_ERR;
373
374 if (RedisModule_CreateCommand(ctx,"testrdb.set.after", testrdb_set_after,"deny-oom",0,0,0) == REDISMODULE_ERR)
375 return REDISMODULE_ERR;
376
377 if (RedisModule_CreateCommand(ctx,"testrdb.get.after", testrdb_get_after,"",0,0,0) == REDISMODULE_ERR)
378 return REDISMODULE_ERR;
379
380 if (RedisModule_CreateCommand(ctx,"testrdb.set.key", testrdb_set_key,"deny-oom",1,1,1) == REDISMODULE_ERR)
381 return REDISMODULE_ERR;
382
383 if (RedisModule_CreateCommand(ctx,"testrdb.get.key", testrdb_get_key,"",1,1,1) == REDISMODULE_ERR)
384 return REDISMODULE_ERR;
385
386 if (RedisModule_CreateCommand(ctx,"testrdb.get.n_aux_load_called", testrdb_get_n_aux_load_called,"",1,1,1) == REDISMODULE_ERR)
387 return REDISMODULE_ERR;
388
389 RedisModule_SubscribeToServerEvent(ctx,
390 RedisModuleEvent_ReplAsyncLoad, replAsyncLoadCallback);
391
392 return REDISMODULE_OK;
393}
394
395int RedisModule_OnUnload(RedisModuleCtx *ctx) {
396 if (before_str)
397 RedisModule_FreeString(ctx, before_str);
398 if (after_str)
399 RedisModule_FreeString(ctx, after_str);
400 if (before_str_temp)
401 RedisModule_FreeString(ctx, before_str_temp);
402 if (after_str_temp)
403 RedisModule_FreeString(ctx, after_str_temp);
404 return REDISMODULE_OK;
405}
diff --git a/examples/redis-unstable/tests/modules/timer.c b/examples/redis-unstable/tests/modules/timer.c
new file mode 100644
index 0000000..c9bd636
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/timer.c
@@ -0,0 +1,102 @@
1
2#include "redismodule.h"
3
4static void timer_callback(RedisModuleCtx *ctx, void *data)
5{
6 RedisModuleString *keyname = data;
7 RedisModuleCallReply *reply;
8
9 reply = RedisModule_Call(ctx, "INCR", "s", keyname);
10 if (reply != NULL)
11 RedisModule_FreeCallReply(reply);
12 RedisModule_FreeString(ctx, keyname);
13}
14
15int test_createtimer(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
16{
17 if (argc != 3) {
18 RedisModule_WrongArity(ctx);
19 return REDISMODULE_OK;
20 }
21
22 long long period;
23 if (RedisModule_StringToLongLong(argv[1], &period) == REDISMODULE_ERR) {
24 RedisModule_ReplyWithError(ctx, "Invalid time specified.");
25 return REDISMODULE_OK;
26 }
27
28 RedisModuleString *keyname = argv[2];
29 RedisModule_RetainString(ctx, keyname);
30
31 RedisModuleTimerID id = RedisModule_CreateTimer(ctx, period, timer_callback, keyname);
32 RedisModule_ReplyWithLongLong(ctx, id);
33
34 return REDISMODULE_OK;
35}
36
37int test_gettimer(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
38{
39 if (argc != 2) {
40 RedisModule_WrongArity(ctx);
41 return REDISMODULE_OK;
42 }
43
44 long long id;
45 if (RedisModule_StringToLongLong(argv[1], &id) == REDISMODULE_ERR) {
46 RedisModule_ReplyWithError(ctx, "Invalid id specified.");
47 return REDISMODULE_OK;
48 }
49
50 uint64_t remaining;
51 RedisModuleString *keyname;
52 if (RedisModule_GetTimerInfo(ctx, id, &remaining, (void **)&keyname) == REDISMODULE_ERR) {
53 RedisModule_ReplyWithNull(ctx);
54 } else {
55 RedisModule_ReplyWithArray(ctx, 2);
56 RedisModule_ReplyWithString(ctx, keyname);
57 RedisModule_ReplyWithLongLong(ctx, remaining);
58 }
59
60 return REDISMODULE_OK;
61}
62
63int test_stoptimer(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
64{
65 if (argc != 2) {
66 RedisModule_WrongArity(ctx);
67 return REDISMODULE_OK;
68 }
69
70 long long id;
71 if (RedisModule_StringToLongLong(argv[1], &id) == REDISMODULE_ERR) {
72 RedisModule_ReplyWithError(ctx, "Invalid id specified.");
73 return REDISMODULE_OK;
74 }
75
76 int ret = 0;
77 RedisModuleString *keyname;
78 if (RedisModule_StopTimer(ctx, id, (void **) &keyname) == REDISMODULE_OK) {
79 RedisModule_FreeString(ctx, keyname);
80 ret = 1;
81 }
82
83 RedisModule_ReplyWithLongLong(ctx, ret);
84 return REDISMODULE_OK;
85}
86
87
88int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
89 REDISMODULE_NOT_USED(argv);
90 REDISMODULE_NOT_USED(argc);
91 if (RedisModule_Init(ctx,"timer",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
92 return REDISMODULE_ERR;
93
94 if (RedisModule_CreateCommand(ctx,"test.createtimer", test_createtimer,"",0,0,0) == REDISMODULE_ERR)
95 return REDISMODULE_ERR;
96 if (RedisModule_CreateCommand(ctx,"test.gettimer", test_gettimer,"",0,0,0) == REDISMODULE_ERR)
97 return REDISMODULE_ERR;
98 if (RedisModule_CreateCommand(ctx,"test.stoptimer", test_stoptimer,"",0,0,0) == REDISMODULE_ERR)
99 return REDISMODULE_ERR;
100
101 return REDISMODULE_OK;
102}
diff --git a/examples/redis-unstable/tests/modules/usercall.c b/examples/redis-unstable/tests/modules/usercall.c
new file mode 100644
index 0000000..cc28817
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/usercall.c
@@ -0,0 +1,230 @@
1#include "redismodule.h"
2#include <pthread.h>
3#include <assert.h>
4
5#define UNUSED(V) ((void) V)
6
7RedisModuleUser *user = NULL;
8
9int call_without_user(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
10 if (argc < 2) {
11 return RedisModule_WrongArity(ctx);
12 }
13
14 const char *cmd = RedisModule_StringPtrLen(argv[1], NULL);
15
16 RedisModuleCallReply *rep = RedisModule_Call(ctx, cmd, "Ev", argv + 2, (size_t)argc - 2);
17 if (!rep) {
18 RedisModule_ReplyWithError(ctx, "NULL reply returned");
19 } else {
20 RedisModule_ReplyWithCallReply(ctx, rep);
21 RedisModule_FreeCallReply(rep);
22 }
23 return REDISMODULE_OK;
24}
25
26int call_with_user_flag(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
27 if (argc < 3) {
28 return RedisModule_WrongArity(ctx);
29 }
30
31 RedisModule_SetContextUser(ctx, user);
32
33 /* Append Ev to the provided flags. */
34 RedisModuleString *flags = RedisModule_CreateStringFromString(ctx, argv[1]);
35 RedisModule_StringAppendBuffer(ctx, flags, "Ev", 2);
36
37 const char* flg = RedisModule_StringPtrLen(flags, NULL);
38 const char* cmd = RedisModule_StringPtrLen(argv[2], NULL);
39
40 RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, flg, argv + 3, (size_t)argc - 3);
41 if (!rep) {
42 RedisModule_ReplyWithError(ctx, "NULL reply returned");
43 } else {
44 RedisModule_ReplyWithCallReply(ctx, rep);
45 RedisModule_FreeCallReply(rep);
46 }
47 RedisModule_FreeString(ctx, flags);
48
49 return REDISMODULE_OK;
50}
51
52int add_to_acl(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
53 if (argc != 2) {
54 return RedisModule_WrongArity(ctx);
55 }
56
57 size_t acl_len;
58 const char *acl = RedisModule_StringPtrLen(argv[1], &acl_len);
59
60 RedisModuleString *error;
61 int ret = RedisModule_SetModuleUserACLString(ctx, user, acl, &error);
62 if (ret) {
63 size_t len;
64 const char * e = RedisModule_StringPtrLen(error, &len);
65 RedisModule_ReplyWithError(ctx, e);
66 return REDISMODULE_OK;
67 }
68
69 RedisModule_ReplyWithSimpleString(ctx, "OK");
70
71 return REDISMODULE_OK;
72}
73
74int get_acl(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
75 REDISMODULE_NOT_USED(argv);
76
77 if (argc != 1) {
78 return RedisModule_WrongArity(ctx);
79 }
80
81 RedisModule_Assert(user != NULL);
82
83 RedisModuleString *acl = RedisModule_GetModuleUserACLString(user);
84
85 RedisModule_ReplyWithString(ctx, acl);
86
87 RedisModule_FreeString(NULL, acl);
88
89 return REDISMODULE_OK;
90}
91
92int reset_user(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
93 REDISMODULE_NOT_USED(argv);
94
95 if (argc != 1) {
96 return RedisModule_WrongArity(ctx);
97 }
98
99 if (user != NULL) {
100 RedisModule_FreeModuleUser(user);
101 }
102
103 user = RedisModule_CreateModuleUser("module_user");
104
105 RedisModule_ReplyWithSimpleString(ctx, "OK");
106
107 return REDISMODULE_OK;
108}
109
110typedef struct {
111 RedisModuleString **argv;
112 int argc;
113 RedisModuleBlockedClient *bc;
114} bg_call_data;
115
116void *bg_call_worker(void *arg) {
117 bg_call_data *bg = arg;
118 RedisModuleBlockedClient *bc = bg->bc;
119
120 // Get Redis module context
121 RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bg->bc);
122
123 // Acquire GIL
124 RedisModule_ThreadSafeContextLock(ctx);
125
126 // Set user
127 RedisModule_SetContextUser(ctx, user);
128
129 // Call the command
130 size_t format_len;
131 RedisModuleString *format_redis_str = RedisModule_CreateString(NULL, "v", 1);
132 const char *format = RedisModule_StringPtrLen(bg->argv[1], &format_len);
133 RedisModule_StringAppendBuffer(NULL, format_redis_str, format, format_len);
134 RedisModule_StringAppendBuffer(NULL, format_redis_str, "E", 1);
135 format = RedisModule_StringPtrLen(format_redis_str, NULL);
136 const char *cmd = RedisModule_StringPtrLen(bg->argv[2], NULL);
137 RedisModuleCallReply *rep = RedisModule_Call(ctx, cmd, format, bg->argv + 3, (size_t)bg->argc - 3);
138 RedisModule_FreeString(NULL, format_redis_str);
139
140 /* Free the arguments within GIL to prevent simultaneous freeing in main thread. */
141 for (int i=0; i<bg->argc; i++)
142 RedisModule_FreeString(ctx, bg->argv[i]);
143 RedisModule_Free(bg->argv);
144 RedisModule_Free(bg);
145
146 // Release GIL
147 RedisModule_ThreadSafeContextUnlock(ctx);
148
149 // Reply to client
150 if (!rep) {
151 RedisModule_ReplyWithError(ctx, "NULL reply returned");
152 } else {
153 RedisModule_ReplyWithCallReply(ctx, rep);
154 RedisModule_FreeCallReply(rep);
155 }
156
157 // Unblock client
158 RedisModule_UnblockClient(bc, NULL);
159
160 // Free the Redis module context
161 RedisModule_FreeThreadSafeContext(ctx);
162
163 return NULL;
164}
165
166int call_with_user_bg(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
167{
168 UNUSED(argv);
169 UNUSED(argc);
170
171 /* Make sure we're not trying to block a client when we shouldn't */
172 int flags = RedisModule_GetContextFlags(ctx);
173 int allFlags = RedisModule_GetContextFlagsAll();
174 if ((allFlags & REDISMODULE_CTX_FLAGS_MULTI) &&
175 (flags & REDISMODULE_CTX_FLAGS_MULTI)) {
176 RedisModule_ReplyWithSimpleString(ctx, "Blocked client is not supported inside multi");
177 return REDISMODULE_OK;
178 }
179 if ((allFlags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING) &&
180 (flags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING)) {
181 RedisModule_ReplyWithSimpleString(ctx, "Blocked client is not allowed");
182 return REDISMODULE_OK;
183 }
184
185 /* Make a copy of the arguments and pass them to the thread. */
186 bg_call_data *bg = RedisModule_Alloc(sizeof(bg_call_data));
187 bg->argv = RedisModule_Alloc(sizeof(RedisModuleString*)*argc);
188 bg->argc = argc;
189 for (int i=0; i<argc; i++)
190 bg->argv[i] = RedisModule_HoldString(ctx, argv[i]);
191
192 /* Block the client */
193 bg->bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0);
194
195 /* Start a thread to handle the request */
196 pthread_t tid;
197 int res = pthread_create(&tid, NULL, bg_call_worker, bg);
198 assert(res == 0);
199 pthread_detach(tid);
200
201 return REDISMODULE_OK;
202}
203
204int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
205 REDISMODULE_NOT_USED(argv);
206 REDISMODULE_NOT_USED(argc);
207
208 if (RedisModule_Init(ctx,"usercall",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
209 return REDISMODULE_ERR;
210
211 if (RedisModule_CreateCommand(ctx,"usercall.call_without_user", call_without_user,"write",0,0,0) == REDISMODULE_ERR)
212 return REDISMODULE_ERR;
213
214 if (RedisModule_CreateCommand(ctx,"usercall.call_with_user_flag", call_with_user_flag,"write",0,0,0) == REDISMODULE_ERR)
215 return REDISMODULE_ERR;
216
217 if (RedisModule_CreateCommand(ctx, "usercall.call_with_user_bg", call_with_user_bg, "write", 0, 0, 0) == REDISMODULE_ERR)
218 return REDISMODULE_ERR;
219
220 if (RedisModule_CreateCommand(ctx, "usercall.add_to_acl", add_to_acl, "write",0,0,0) == REDISMODULE_ERR)
221 return REDISMODULE_ERR;
222
223 if (RedisModule_CreateCommand(ctx,"usercall.reset_user", reset_user,"write",0,0,0) == REDISMODULE_ERR)
224 return REDISMODULE_ERR;
225
226 if (RedisModule_CreateCommand(ctx,"usercall.get_acl", get_acl,"write",0,0,0) == REDISMODULE_ERR)
227 return REDISMODULE_ERR;
228
229 return REDISMODULE_OK;
230}
diff --git a/examples/redis-unstable/tests/modules/zset.c b/examples/redis-unstable/tests/modules/zset.c
new file mode 100644
index 0000000..adb318d
--- /dev/null
+++ b/examples/redis-unstable/tests/modules/zset.c
@@ -0,0 +1,180 @@
1#include "redismodule.h"
2#include <math.h>
3#include <errno.h>
4
5/* ZSET.REM key element
6 *
7 * Removes an occurrence of an element from a sorted set. Replies with the
8 * number of removed elements (0 or 1).
9 */
10int zset_rem(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
11 if (argc != 3) return RedisModule_WrongArity(ctx);
12 RedisModule_AutoMemory(ctx);
13 int keymode = REDISMODULE_READ | REDISMODULE_WRITE;
14 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], keymode);
15 int deleted;
16 if (RedisModule_ZsetRem(key, argv[2], &deleted) == REDISMODULE_OK)
17 return RedisModule_ReplyWithLongLong(ctx, deleted);
18 else
19 return RedisModule_ReplyWithError(ctx, "ERR ZsetRem failed");
20}
21
22/* ZSET.ADD key score member
23 *
24 * Adds a specified member with the specified score to the sorted
25 * set stored at key.
26 */
27int zset_add(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
28 if (argc != 4) return RedisModule_WrongArity(ctx);
29 RedisModule_AutoMemory(ctx);
30 int keymode = REDISMODULE_READ | REDISMODULE_WRITE;
31 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], keymode);
32
33 size_t len;
34 double score;
35 char *endptr;
36 const char *str = RedisModule_StringPtrLen(argv[2], &len);
37 score = strtod(str, &endptr);
38 if (*endptr != '\0' || errno == ERANGE)
39 return RedisModule_ReplyWithError(ctx, "value is not a valid float");
40
41 if (RedisModule_ZsetAdd(key, score, argv[3], NULL) == REDISMODULE_OK)
42 return RedisModule_ReplyWithSimpleString(ctx, "OK");
43 else
44 return RedisModule_ReplyWithError(ctx, "ERR ZsetAdd failed");
45}
46
47/* ZSET.INCRBY key member increment
48 *
49 * Increments the score stored at member in the sorted set stored at key by increment.
50 * Replies with the new score of this element.
51 */
52int zset_incrby(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
53 if (argc != 4) return RedisModule_WrongArity(ctx);
54 RedisModule_AutoMemory(ctx);
55 int keymode = REDISMODULE_READ | REDISMODULE_WRITE;
56 RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], keymode);
57
58 size_t len;
59 double score, newscore;
60 char *endptr;
61 const char *str = RedisModule_StringPtrLen(argv[3], &len);
62 score = strtod(str, &endptr);
63 if (*endptr != '\0' || errno == ERANGE)
64 return RedisModule_ReplyWithError(ctx, "value is not a valid float");
65
66 if (RedisModule_ZsetIncrby(key, score, argv[2], NULL, &newscore) == REDISMODULE_OK)
67 return RedisModule_ReplyWithDouble(ctx, newscore);
68 else
69 return RedisModule_ReplyWithError(ctx, "ERR ZsetIncrby failed");
70}
71
72/* Structure to hold data for the delall scan callback */
73typedef struct {
74 RedisModuleCtx *ctx;
75 RedisModuleString **keys_to_delete;
76 size_t keys_capacity;
77 size_t keys_count;
78} zset_delall_data;
79
80/* Callback function for scanning keys and collecting zset keys to delete */
81void zset_delall_callback(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata) {
82 zset_delall_data *data = privdata;
83 int was_opened = 0;
84
85 /* Open the key if it wasn't already opened */
86 if (!key) {
87 key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ);
88 was_opened = 1;
89 }
90
91 /* Check if the key is a zset and add it to the list */
92 if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_ZSET) {
93 /* Expand the array if needed */
94 if (data->keys_count >= data->keys_capacity) {
95 data->keys_capacity = data->keys_capacity ? data->keys_capacity * 2 : 16;
96 data->keys_to_delete = RedisModule_Realloc(data->keys_to_delete,
97 data->keys_capacity * sizeof(RedisModuleString*));
98 }
99
100 /* Store the key name (retain it so it doesn't get freed) */
101 data->keys_to_delete[data->keys_count] = keyname;
102 RedisModule_RetainString(ctx, keyname);
103 data->keys_count++;
104 }
105
106 /* Close the key if we opened it */
107 if (was_opened) {
108 RedisModule_CloseKey(key);
109 }
110}
111
112/* ZSET.DELALL
113 *
114 * Iterates through the keyspace and deletes all keys of type "zset".
115 * Returns the number of deleted keys.
116 */
117int zset_delall(RedisModuleCtx *ctx, REDISMODULE_ATTR_UNUSED RedisModuleString **argv, int argc) {
118 if (argc != 1) return RedisModule_WrongArity(ctx);
119 RedisModule_AutoMemory(ctx);
120
121 zset_delall_data data = {
122 .ctx = ctx,
123 .keys_to_delete = NULL,
124 .keys_capacity = 0,
125 .keys_count = 0
126 };
127
128 /* Create a scan cursor and iterate through all keys */
129 RedisModuleScanCursor *cursor = RedisModule_ScanCursorCreate();
130 while (RedisModule_Scan(ctx, cursor, zset_delall_callback, &data));
131 RedisModule_ScanCursorDestroy(cursor);
132
133 /* Delete all the collected zset keys after scan is complete */
134 size_t deleted_count = 0;
135 for (size_t i = 0; i < data.keys_count; i++) {
136 RedisModuleCallReply *reply = RedisModule_Call(ctx, "DEL", "s!", data.keys_to_delete[i]);
137 if (reply && RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) {
138 long long del_result = RedisModule_CallReplyInteger(reply);
139 if (del_result > 0) {
140 deleted_count++;
141 }
142 }
143 if (reply) {
144 RedisModule_FreeCallReply(reply);
145 }
146 RedisModule_FreeString(ctx, data.keys_to_delete[i]);
147 }
148
149 /* Free the keys array */
150 if (data.keys_to_delete) {
151 RedisModule_Free(data.keys_to_delete);
152 }
153
154 return RedisModule_ReplyWithLongLong(ctx, deleted_count);
155}
156
157int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
158 REDISMODULE_NOT_USED(argv);
159 REDISMODULE_NOT_USED(argc);
160 if (RedisModule_Init(ctx, "zset", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
161 return REDISMODULE_ERR;
162
163 if (RedisModule_CreateCommand(ctx, "zset.rem", zset_rem, "write",
164 1, 1, 1) == REDISMODULE_ERR)
165 return REDISMODULE_ERR;
166
167 if (RedisModule_CreateCommand(ctx, "zset.add", zset_add, "write",
168 1, 1, 1) == REDISMODULE_ERR)
169 return REDISMODULE_ERR;
170
171 if (RedisModule_CreateCommand(ctx, "zset.incrby", zset_incrby, "write",
172 1, 1, 1) == REDISMODULE_ERR)
173 return REDISMODULE_ERR;
174
175 if (RedisModule_CreateCommand(ctx, "zset.delall", zset_delall, "write touches-arbitrary-keys",
176 0, 0, 0) == REDISMODULE_ERR)
177 return REDISMODULE_ERR;
178
179 return REDISMODULE_OK;
180}