Skip to content

Commit d589127

Browse files
authored
Merge pull request #15 from yossigo/fix-raftize-reentrancy
Fix raftize re-entrancy issues (#13, #14).
2 parents 1b3fbf6 + 634d9a5 commit d589127

File tree

4 files changed

+70
-1
lines changed

4 files changed

+70
-1
lines changed

common.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include "redisraft.h"
44

5+
int redis_raft_in_rm_call = 0;
6+
57
const char *getStateStr(RedisRaftCtx *rr)
68
{
79
static const char *state_str[] = { "uninitialized", "up", "loading", "joining" };
@@ -144,6 +146,13 @@ static void raftize_commands(RedisModuleCommandFilterCtx *filter)
144146
NULL
145147
};
146148

149+
/* If we're intercepting an RM_Call() processing a Raft entry,
150+
* skip.
151+
*/
152+
if (checkInRedisModuleCall()) {
153+
return;
154+
}
155+
147156
size_t cmdname_len;
148157
const char *cmdname = RedisModule_StringPtrLen(
149158
RedisModule_CommandFilterArgGet(filter, 0), &cmdname_len);

raft.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,10 @@ static void executeRaftRedisCommandArray(RaftRedisCommandArray *array,
154154
continue;
155155
}
156156

157+
enterRedisModuleCall();
157158
RedisModuleCallReply *reply = RedisModule_Call(
158159
ctx, cmd, "v", &c->argv[1], c->argc - 1);
160+
exitRedisModuleCall();
159161

160162
if (reply_ctx) {
161163
if (reply) {

redisraft.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,33 @@ typedef struct SnapshotResult {
361361
char err[256];
362362
} SnapshotResult;
363363

364+
/* Command filtering re-entrancy counter handling.
365+
*
366+
* This mechanim tracks calls from Redis Raft into Redis and used by the
367+
* command filtering hook to avoid raftizing commands as they're pushed from the log
368+
* to the FSM.
369+
*
370+
* Redis Module API provides the REDISMODULE_CMDFILTER_NOSELF flag which does
371+
* the same thing, but does not apply to executions from a thread safe context.
372+
*
373+
* This must wrap every call to RedisModule_Call(), after the Redis lock has been
374+
* acquired, and unless the called command is known to be excluded from raftizing.
375+
*/
376+
377+
extern int redis_raft_in_rm_call; /* defined in common.c */
378+
379+
static void inline enterRedisModuleCall(void) {
380+
redis_raft_in_rm_call++;
381+
}
382+
383+
static void inline exitRedisModuleCall(void) {
384+
redis_raft_in_rm_call--;
385+
}
386+
387+
static int inline checkInRedisModuleCall(void) {
388+
return redis_raft_in_rm_call;
389+
}
390+
364391
/* common.c */
365392
const char *getStateStr(RedisRaftCtx *rr);
366393
const char *raft_logtype_str(int type);

tests/integration/test_sanity.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from pytest import raises, skip
33
from fixtures import cluster
44
from sandbox import RedisRaft
5-
5+
import time
66

77
def test_add_node_as_a_single_leader(cluster):
88
"""
@@ -182,3 +182,34 @@ def test_raftize_does_not_affect_lua(cluster):
182182
assert r1.client.get('key1') == b'value1'
183183
assert r1.client.get('key2') == b'value2'
184184
assert r1.client.get('key3') == b'value3'
185+
186+
def test_proxying_with_raftize(cluster):
187+
"""
188+
Test follower proxy mode together with raftize enabled.
189+
190+
This is a regression test for issues #13, #14 and basically ensures
191+
that command filtering does not trigger re-entrancy when processing
192+
a log entry over a thread safe context.
193+
"""
194+
cluster.create(3)
195+
assert cluster.leader == 1
196+
197+
assert cluster.node(1).client.execute_command(
198+
'RAFT.CONFIG', 'SET', 'follower-proxy', 'yes') == b'OK'
199+
assert cluster.node(2).client.execute_command(
200+
'RAFT.CONFIG', 'SET', 'follower-proxy', 'yes') == b'OK'
201+
assert cluster.node(3).client.execute_command(
202+
'RAFT.CONFIG', 'SET', 'follower-proxy', 'yes') == b'OK'
203+
assert cluster.node(1).client.execute_command(
204+
'RAFT.CONFIG', 'SET', 'raftize-all-commands', 'yes') == b'OK'
205+
assert cluster.node(2).client.execute_command(
206+
'RAFT.CONFIG', 'SET', 'raftize-all-commands', 'yes') == b'OK'
207+
assert cluster.node(3).client.execute_command(
208+
'RAFT.CONFIG', 'SET', 'raftize-all-commands', 'yes') == b'OK'
209+
210+
assert cluster.node(1).raft_exec('RPUSH', 'list-a', 'x') == 1
211+
assert cluster.node(1).raft_exec('RPUSH', 'list-a', 'x') == 2
212+
assert cluster.node(1).raft_exec('RPUSH', 'list-a', 'x') == 3
213+
214+
time.sleep(1)
215+
assert cluster.node(1).raft_info()['current_index'] == 8

0 commit comments

Comments
 (0)