summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/tests/modules/eventloop.c
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/eventloop.c
parentc7ab12bba64d9c20ccd79b132dac475f7bc3923e (diff)
downloadcrep-5d8dfe892a2ea89f706ee140c3bdcfd89fe03fda.tar.gz
Add Redis source code for testing
Diffstat (limited to 'examples/redis-unstable/tests/modules/eventloop.c')
-rw-r--r--examples/redis-unstable/tests/modules/eventloop.c276
1 files changed, 276 insertions, 0 deletions
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}