Unverified Commit 1b0968df authored by zhugezy's avatar zhugezy Committed by GitHub
Browse files

Remove EVAL script verbatim replication, propagation, and deterministic execution logic (#9812)



# Background

The main goal of this PR is to remove relevant logics on Lua script verbatim replication,
only keeping effects replication logic, which has been set as default since Redis 5.0.
As a result, Lua in Redis 7.0 would be acting the same as Redis 6.0 with default
configuration from users' point of view.

There are lots of reasons to remove verbatim replication.
Antirez has listed some of the benefits in Issue #5292:

>1. No longer need to explain to users side effects into scripts.
    They can do whatever they want.
>2. No need for a cache about scripts that we sent or not to the slaves.
>3. No need to sort the output of certain commands inside scripts
    (SMEMBERS and others): this both simplifies and gains speed.
>4. No need to store scripts inside the RDB file in order to startup correctly.
>5. No problems about evicting keys during the script execution.

When looking back at Redis 5.0, antirez and core team decided to set the config
`lua-replicate-commands yes` by default instead of removing verbatim replication
directly, in case some bad situations happened. 3 years later now before Redis 7.0,
it's time to remove it formally.

# Changes

- configuration for lua-replicate-commands removed
  - created config file stub for backward compatibility
- Replication script cache removed
  - this is useless under script effects replication
  - relevant statistics also removed
- script persistence in RDB files is also removed
- Propagation of SCRIPT LOAD and SCRIPT FLUSH to replica / AOF removed
- Deterministic execution logic in scripts removed (i.e. don't run write commands
  after random ones, and sorting output of commands with random order)
  - the flags indicating which commands have non-deterministic results are kept as hints to clients.
- `redis.replicate_commands()` & `redis.set_repl()` changed
  - now `redis.replicate_commands()` does nothing and return an 1
  - ...and then `redis.set_repl()` can be issued before `redis.replicate_commands()` now
- Relevant TCL cases adjusted
- DEBUG lua-always-replicate-commands removed

# Other changes
- Fix a recent bug comparing CLIENT_ID_AOF to original_client->flags instead of id. (introduced in #9780)
Co-authored-by: default avatarOran Agra <oran@redislabs.com>
parent febc3f63
...@@ -1777,7 +1777,6 @@ int rewriteAppendOnlyFileBackground(void) { ...@@ -1777,7 +1777,6 @@ int rewriteAppendOnlyFileBackground(void) {
* accumulated by the parent into server.aof_rewrite_buf will start * accumulated by the parent into server.aof_rewrite_buf will start
* with a SELECT statement and it will be safe to merge. */ * with a SELECT statement and it will be safe to merge. */
server.aof_selected_db = -1; server.aof_selected_db = -1;
replicationScriptCacheFlush();
return C_OK; return C_OK;
} }
return C_OK; /* unreached */ return C_OK; /* unreached */
......
...@@ -3247,10 +3247,10 @@ struct redisCommandArg SCRIPT_LOAD_Args[] = { ...@@ -3247,10 +3247,10 @@ struct redisCommandArg SCRIPT_LOAD_Args[] = {
struct redisCommand SCRIPT_Subcommands[] = { struct redisCommand SCRIPT_Subcommands[] = {
{"debug","Set the debug mode for executed scripts.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_DEBUG_History,SCRIPT_DEBUG_Hints,scriptCommand,3,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,.args=SCRIPT_DEBUG_Args}, {"debug","Set the debug mode for executed scripts.","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_DEBUG_History,SCRIPT_DEBUG_Hints,scriptCommand,3,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,.args=SCRIPT_DEBUG_Args},
{"exists","Check existence of scripts in the script cache.","O(N) with N being the number of scripts to check (so checking a single script is an O(1) operation).","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_EXISTS_History,SCRIPT_EXISTS_Hints,scriptCommand,-3,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,.args=SCRIPT_EXISTS_Args}, {"exists","Check existence of scripts in the script cache.","O(N) with N being the number of scripts to check (so checking a single script is an O(1) operation).","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_EXISTS_History,SCRIPT_EXISTS_Hints,scriptCommand,-3,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,.args=SCRIPT_EXISTS_Args},
{"flush","Remove all the scripts from the script cache.","O(N) with N being the number of scripts in cache","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_FLUSH_History,SCRIPT_FLUSH_Hints,scriptCommand,-2,CMD_NOSCRIPT|CMD_MAY_REPLICATE,ACL_CATEGORY_SCRIPTING,.args=SCRIPT_FLUSH_Args}, {"flush","Remove all the scripts from the script cache.","O(N) with N being the number of scripts in cache","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_FLUSH_History,SCRIPT_FLUSH_Hints,scriptCommand,-2,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,.args=SCRIPT_FLUSH_Args},
{"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_HELP_History,SCRIPT_HELP_Hints,scriptCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SCRIPTING}, {"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_HELP_History,SCRIPT_HELP_Hints,scriptCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_SCRIPTING},
{"kill","Kill the script currently in execution.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_KILL_History,SCRIPT_KILL_Hints,scriptCommand,2,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING}, {"kill","Kill the script currently in execution.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_KILL_History,SCRIPT_KILL_Hints,scriptCommand,2,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING},
{"load","Load the specified Lua script into the script cache.","O(N) with N being the length in bytes of the script body.","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_LOAD_History,SCRIPT_LOAD_Hints,scriptCommand,3,CMD_NOSCRIPT|CMD_MAY_REPLICATE,ACL_CATEGORY_SCRIPTING,.args=SCRIPT_LOAD_Args}, {"load","Load the specified Lua script into the script cache.","O(N) with N being the length in bytes of the script body.","2.6.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SCRIPTING,SCRIPT_LOAD_History,SCRIPT_LOAD_Hints,scriptCommand,3,CMD_NOSCRIPT,ACL_CATEGORY_SCRIPTING,.args=SCRIPT_LOAD_Args},
{0} {0}
}; };
......
...@@ -14,8 +14,7 @@ ...@@ -14,8 +14,7 @@
] ]
], ],
"command_flags": [ "command_flags": [
"NOSCRIPT", "NOSCRIPT"
"MAY_REPLICATE"
], ],
"acl_categories": [ "acl_categories": [
"SCRIPTING" "SCRIPTING"
......
...@@ -8,8 +8,7 @@ ...@@ -8,8 +8,7 @@
"container": "SCRIPT", "container": "SCRIPT",
"function": "scriptCommand", "function": "scriptCommand",
"command_flags": [ "command_flags": [
"NOSCRIPT", "NOSCRIPT"
"MAY_REPLICATE"
], ],
"acl_categories": [ "acl_categories": [
"SCRIPTING" "SCRIPTING"
......
...@@ -45,6 +45,12 @@ typedef struct configEnum { ...@@ -45,6 +45,12 @@ typedef struct configEnum {
const int val; const int val;
} configEnum; } configEnum;
typedef struct deprecatedConfig {
const char *name;
const int argc_min;
const int argc_max;
} deprecatedConfig;
configEnum maxmemory_policy_enum[] = { configEnum maxmemory_policy_enum[] = {
{"volatile-lru", MAXMEMORY_VOLATILE_LRU}, {"volatile-lru", MAXMEMORY_VOLATILE_LRU},
{"volatile-lfu", MAXMEMORY_VOLATILE_LFU}, {"volatile-lfu", MAXMEMORY_VOLATILE_LFU},
...@@ -405,6 +411,12 @@ void initConfigValues() { ...@@ -405,6 +411,12 @@ void initConfigValues() {
static int reading_config_file; static int reading_config_file;
void loadServerConfigFromString(char *config) { void loadServerConfigFromString(char *config) {
deprecatedConfig deprecated_configs[] = {
{"list-max-ziplist-entries", 2, 2},
{"list-max-ziplist-value", 2, 2},
{"lua-replicate-commands", 2, 2},
{NULL, 0},
};
char buf[1024]; char buf[1024];
const char *err = NULL; const char *err = NULL;
int linenum = 0, totlines, i; int linenum = 0, totlines, i;
...@@ -459,6 +471,19 @@ void loadServerConfigFromString(char *config) { ...@@ -459,6 +471,19 @@ void loadServerConfigFromString(char *config) {
} }
} }
/* If there's no matching above, we try matching them with deprecated configs */
if (!match) {
for (deprecatedConfig *config = deprecated_configs; config->name != NULL; config++) {
if (!strcasecmp(argv[0], config->name) &&
config->argc_min <= argc &&
argc <= config->argc_max)
{
match = 1;
break;
}
}
}
if (match) { if (match) {
sdsfreesplitres(argv,argc); sdsfreesplitres(argv,argc);
continue; continue;
...@@ -467,10 +492,6 @@ void loadServerConfigFromString(char *config) { ...@@ -467,10 +492,6 @@ void loadServerConfigFromString(char *config) {
/* Execute config directives */ /* Execute config directives */
if (!strcasecmp(argv[0],"include") && argc == 2) { if (!strcasecmp(argv[0],"include") && argc == 2) {
loadServerConfig(argv[1], 0, NULL); loadServerConfig(argv[1], 0, NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) { } else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
struct redisCommand *cmd = lookupCommandBySds(argv[1]); struct redisCommand *cmd = lookupCommandBySds(argv[1]);
int retval; int retval;
...@@ -2572,7 +2593,6 @@ standardConfig configs[] = { ...@@ -2572,7 +2593,6 @@ standardConfig configs[] = {
createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL), createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL),
createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL), createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL),
createBoolConfig("io-threads-do-reads", NULL, DEBUG_CONFIG | IMMUTABLE_CONFIG, server.io_threads_do_reads, 0,NULL, NULL), /* Read + parse from threads? */ createBoolConfig("io-threads-do-reads", NULL, DEBUG_CONFIG | IMMUTABLE_CONFIG, server.io_threads_do_reads, 0,NULL, NULL), /* Read + parse from threads? */
createBoolConfig("lua-replicate-commands", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, server.lua_always_replicate_commands, 1, NULL, NULL),
createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, server.always_show_logo, 0, NULL, NULL), createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, server.always_show_logo, 0, NULL, NULL),
createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, server.protected_mode, 1, NULL, NULL), createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, server.protected_mode, 1, NULL, NULL),
createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, server.rdb_compression, 1, NULL, NULL), createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, server.rdb_compression, 1, NULL, NULL),
......
...@@ -417,9 +417,6 @@ void debugCommand(client *c) { ...@@ -417,9 +417,6 @@ void debugCommand(client *c) {
" Like HTSTATS but for the hash table stored at <key>'s value.", " Like HTSTATS but for the hash table stored at <key>'s value.",
"LOADAOF", "LOADAOF",
" Flush the AOF buffers on disk and reload the AOF in memory.", " Flush the AOF buffers on disk and reload the AOF in memory.",
"LUA-ALWAYS-REPLICATE-COMMANDS <0|1>",
" Setting it to 1 makes Lua replication defaulting to replicating single",
" commands, without the script having to enable effects replication.",
#ifdef USE_JEMALLOC #ifdef USE_JEMALLOC
"MALLCTL <key> [<val>]", "MALLCTL <key> [<val>]",
" Get or set a malloc tuning integer.", " Get or set a malloc tuning integer.",
...@@ -832,11 +829,6 @@ NULL ...@@ -832,11 +829,6 @@ NULL
{ {
server.aof_flush_sleep = atoi(c->argv[2]->ptr); server.aof_flush_sleep = atoi(c->argv[2]->ptr);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"lua-always-replicate-commands") &&
c->argc == 3)
{
server.lua_always_replicate_commands = atoi(c->argv[2]->ptr);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"error") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"error") && c->argc == 3) {
sds errstr = sdsnewlen("-",1); sds errstr = sdsnewlen("-",1);
......
...@@ -940,7 +940,6 @@ long defragOtherGlobals() { ...@@ -940,7 +940,6 @@ long defragOtherGlobals() {
* but we assume most of these are short lived, we only need to defrag allocations * but we assume most of these are short lived, we only need to defrag allocations
* that remain static for a long time */ * that remain static for a long time */
defragged += activeDefragSdsDict(evalScriptsDict(), DEFRAG_SDS_DICT_VAL_IS_STROB); defragged += activeDefragSdsDict(evalScriptsDict(), DEFRAG_SDS_DICT_VAL_IS_STROB);
defragged += activeDefragSdsListAndDict(server.repl_scriptcache_fifo, server.repl_scriptcache_dict, DEFRAG_SDS_DICT_NO_VAL);
defragged += moduleDefragGlobals(); defragged += moduleDefragGlobals();
return defragged; return defragged;
} }
......
...@@ -54,7 +54,6 @@ struct luaCtx { ...@@ -54,7 +54,6 @@ struct luaCtx {
char *lua_cur_script; /* SHA1 of the script currently running, or NULL */ char *lua_cur_script; /* SHA1 of the script currently running, or NULL */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */ unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
int lua_replicate_commands; /* True if we are doing single commands repl. */
} lctx; } lctx;
/* Debugger shared state is stored inside this global structure. */ /* Debugger shared state is stored inside this global structure. */
...@@ -140,23 +139,13 @@ int luaRedisDebugCommand(lua_State *lua) { ...@@ -140,23 +139,13 @@ int luaRedisDebugCommand(lua_State *lua) {
/* redis.replicate_commands() /* redis.replicate_commands()
* *
* DEPRECATED: Now do nothing and always return true.
* Turn on single commands replication if the script never called * Turn on single commands replication if the script never called
* a write command so far, and returns true. Otherwise if the script * a write command so far, and returns true. Otherwise if the script
* already started to write, returns false and stick to whole scripts * already started to write, returns false and stick to whole scripts
* replication, which is our default. */ * replication, which is our default. */
int luaRedisReplicateCommandsCommand(lua_State *lua) { int luaRedisReplicateCommandsCommand(lua_State *lua) {
scriptRunCtx* rctx = luaGetFromRegistry(lua, REGISTRY_RUN_CTX_NAME); lua_pushboolean(lua,1);
if (rctx->flags & SCRIPT_WRITE_DIRTY) {
lua_pushboolean(lua,0);
} else {
lctx.lua_replicate_commands = 1;
rctx->flags &= ~SCRIPT_EVAL_REPLICATION;
/* When we switch to single commands replication, we can provide
* different math.random() sequences at every call, which is what
* the user normally expects. */
redisSrand48(rand());
lua_pushboolean(lua,1);
}
return 1; return 1;
} }
...@@ -373,13 +362,6 @@ void evalGenericCommand(client *c, int evalsha) { ...@@ -373,13 +362,6 @@ void evalGenericCommand(client *c, int evalsha) {
lua_State *lua = lctx.lua; lua_State *lua = lctx.lua;
char funcname[43]; char funcname[43];
long long numkeys; long long numkeys;
long long initial_server_dirty = server.dirty;
/* When we replicate whole scripts, we want the same PRNG sequence at
* every call so that our PRNG is not affected by external state. */
redisSrand48(0);
lctx.lua_replicate_commands = server.lua_always_replicate_commands;
/* Get the number of arguments that are keys */ /* Get the number of arguments that are keys */
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK) if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK)
...@@ -445,7 +427,7 @@ void evalGenericCommand(client *c, int evalsha) { ...@@ -445,7 +427,7 @@ void evalGenericCommand(client *c, int evalsha) {
scriptPrepareForRun(&rctx, lctx.lua_client, c, lctx.lua_cur_script); scriptPrepareForRun(&rctx, lctx.lua_client, c, lctx.lua_cur_script);
rctx.flags |= SCRIPT_EVAL_MODE; /* mark the current run as legacy so we rctx.flags |= SCRIPT_EVAL_MODE; /* mark the current run as legacy so we
will get legacy error messages and logs */ will get legacy error messages and logs */
if (!lctx.lua_replicate_commands) rctx.flags |= SCRIPT_EVAL_REPLICATION;
/* This check is for EVAL_RO, EVALSHA_RO. We want to allow only read only commands */ /* This check is for EVAL_RO, EVALSHA_RO. We want to allow only read only commands */
if ((server.script_caller->cmd->proc == evalRoCommand || if ((server.script_caller->cmd->proc == evalRoCommand ||
server.script_caller->cmd->proc == evalShaRoCommand)) { server.script_caller->cmd->proc == evalShaRoCommand)) {
...@@ -457,43 +439,6 @@ void evalGenericCommand(client *c, int evalsha) { ...@@ -457,43 +439,6 @@ void evalGenericCommand(client *c, int evalsha) {
scriptResetRun(&rctx); scriptResetRun(&rctx);
lctx.lua_cur_script = NULL; lctx.lua_cur_script = NULL;
/* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless
* we are sure that the script was already in the context of all the
* attached slaves *and* the current AOF file if enabled.
*
* To do so we use a cache of SHA1s of scripts that we already propagated
* as full EVAL, that's called the Replication Script Cache.
*
* For replication, every time a new slave attaches to the master, we need to
* flush our cache of scripts that can be replicated as EVALSHA, while
* for AOF we need to do so every time we rewrite the AOF file. */
if (evalsha && !lctx.lua_replicate_commands) {
if (!replicationScriptCacheExists(c->argv[1]->ptr)) {
/* This script is not in our script cache, replicate it as
* EVAL, then add it into the script cache, as from now on
* slaves and AOF know about it. */
robj *script = dictFetchValue(lctx.lua_scripts,c->argv[1]->ptr);
replicationScriptCacheAdd(c->argv[1]->ptr);
serverAssertWithInfo(c,NULL,script != NULL);
/* If the script did not produce any changes in the dataset we want
* just to replicate it as SCRIPT LOAD, otherwise we risk running
* an aborted script on slaves (that may then produce results there)
* or just running a CPU costly read-only script on the slaves. */
if (server.dirty == initial_server_dirty) {
rewriteClientCommandVector(c,3,
shared.script,
shared.load,
script);
} else {
rewriteClientCommandArgument(c,0,shared.eval);
rewriteClientCommandArgument(c,1,script);
}
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
}
}
} }
void evalCommand(client *c) { void evalCommand(client *c) {
...@@ -568,8 +513,6 @@ NULL ...@@ -568,8 +513,6 @@ NULL
} }
scriptingReset(async); scriptingReset(async);
addReply(c,shared.ok); addReply(c,shared.ok);
replicationScriptCacheFlush();
server.dirty++; /* Propagating this command is a good idea. */
} else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists")) { } else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists")) {
int j; int j;
...@@ -584,7 +527,6 @@ NULL ...@@ -584,7 +527,6 @@ NULL
sds sha = luaCreateFunction(c,c->argv[2]); sds sha = luaCreateFunction(c,c->argv[2]);
if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */ if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */
addReplyBulkCBuffer(c,sha,40); addReplyBulkCBuffer(c,sha,40);
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) { } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
scriptKill(c, 1); scriptKill(c, 1);
} else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"debug")) { } else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"debug")) {
...@@ -1389,7 +1331,7 @@ void ldbEval(lua_State *lua, sds *argv, int argc) { ...@@ -1389,7 +1331,7 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
* implementation, with ldb.step enabled, so as a side effect the Redis command * implementation, with ldb.step enabled, so as a side effect the Redis command
* and its reply are logged. */ * and its reply are logged. */
void ldbRedis(lua_State *lua, sds *argv, int argc) { void ldbRedis(lua_State *lua, sds *argv, int argc) {
int j, saved_rc = lctx.lua_replicate_commands; int j;
if (!lua_checkstack(lua, argc + 1)) { if (!lua_checkstack(lua, argc + 1)) {
/* Increase the Lua stack if needed to make sure there is enough room /* Increase the Lua stack if needed to make sure there is enough room
...@@ -1408,10 +1350,8 @@ void ldbRedis(lua_State *lua, sds *argv, int argc) { ...@@ -1408,10 +1350,8 @@ void ldbRedis(lua_State *lua, sds *argv, int argc) {
for (j = 1; j < argc; j++) for (j = 1; j < argc; j++)
lua_pushlstring(lua,argv[j],sdslen(argv[j])); lua_pushlstring(lua,argv[j],sdslen(argv[j]));
ldb.step = 1; /* Force redis.call() to log. */ ldb.step = 1; /* Force redis.call() to log. */
lctx.lua_replicate_commands = 1;
lua_pcall(lua,argc-1,1,0); /* Stack: redis, result */ lua_pcall(lua,argc-1,1,0); /* Stack: redis, result */
ldb.step = 0; /* Disable logging. */ ldb.step = 0; /* Disable logging. */
lctx.lua_replicate_commands = saved_rc;
lua_pop(lua,2); /* Discard the result and clean the stack. */ lua_pop(lua,2); /* Discard the result and clean the stack. */
} }
......
...@@ -1208,12 +1208,6 @@ struct redisMemOverhead *getMemoryOverheadData(void) { ...@@ -1208,12 +1208,6 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mem_total+=mem; mem_total+=mem;
mem = evalScriptsMemory(); mem = evalScriptsMemory();
mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
if (listLength(server.repl_scriptcache_fifo) > 0) {
mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo))));
}
mh->lua_caches = mem; mh->lua_caches = mem;
mem_total+=mem; mem_total+=mem;
mh->functions_caches = functionsMemoryOverhead(); mh->functions_caches = functionsMemoryOverhead();
......
...@@ -1319,21 +1319,6 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) { ...@@ -1319,21 +1319,6 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
di = NULL; /* So that we don't release it again on error. */ di = NULL; /* So that we don't release it again on error. */
} }
/* If we are storing the replication information on disk, persist
* the script cache as well: on successful PSYNC after a restart, we need
* to be able to process any EVALSHA inside the replication backlog the
* master will send us. */
if (rsi && dictSize(evalScriptsDict())) {
di = dictGetIterator(evalScriptsDict());
while((de = dictNext(di)) != NULL) {
robj *body = dictGetVal(de);
if (rdbSaveAuxField(rdb,"lua",3,body->ptr,sdslen(body->ptr)) == -1)
goto werr;
}
dictReleaseIterator(di);
di = NULL; /* So that we don't release it again on error. */
}
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr; if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
/* EOF opcode */ /* EOF opcode */
...@@ -2900,12 +2885,7 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin ...@@ -2900,12 +2885,7 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
} else if (!strcasecmp(auxkey->ptr,"repl-offset")) { } else if (!strcasecmp(auxkey->ptr,"repl-offset")) {
if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10); if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10);
} else if (!strcasecmp(auxkey->ptr,"lua")) { } else if (!strcasecmp(auxkey->ptr,"lua")) {
/* Load the script back in memory. */ /* Won't load the script back in memory anymore. */
if (luaCreateFunction(NULL, auxval) == NULL) {
rdbReportCorruptRDB(
"Can't load Lua script from RDB file! "
"BODY: %s", (char*)auxval->ptr);
}
} else if (!strcasecmp(auxkey->ptr,"redis-ver")) { } else if (!strcasecmp(auxkey->ptr,"redis-ver")) {
serverLog(LL_NOTICE,"Loading RDB produced by version %s", serverLog(LL_NOTICE,"Loading RDB produced by version %s",
(char*)auxval->ptr); (char*)auxval->ptr);
......
...@@ -887,9 +887,6 @@ int startBgsaveForReplication(int mincapa) { ...@@ -887,9 +887,6 @@ int startBgsaveForReplication(int mincapa) {
} }
} }
/* Flush the script cache, since we need that slave differences are
* accumulated without requiring slaves to match our cached scripts. */
if (retval == C_OK) replicationScriptCacheFlush();
return retval; return retval;
} }
...@@ -3277,90 +3274,6 @@ void refreshGoodSlavesCount(void) { ...@@ -3277,90 +3274,6 @@ void refreshGoodSlavesCount(void) {
server.repl_good_slaves_count = good; server.repl_good_slaves_count = good;
} }
/* ----------------------- REPLICATION SCRIPT CACHE --------------------------
* The goal of this code is to keep track of scripts already sent to every
* connected slave, in order to be able to replicate EVALSHA as it is without
* translating it to EVAL every time it is possible.
*
* We use a capped collection implemented by a hash table for fast lookup
* of scripts we can send as EVALSHA, plus a linked list that is used for
* eviction of the oldest entry when the max number of items is reached.
*
* We don't care about taking a different cache for every different slave
* since to fill the cache again is not very costly, the goal of this code
* is to avoid that the same big script is transmitted a big number of times
* per second wasting bandwidth and processor speed, but it is not a problem
* if we need to rebuild the cache from scratch from time to time, every used
* script will need to be transmitted a single time to reappear in the cache.
*
* This is how the system works:
*
* 1) Every time a new slave connects, we flush the whole script cache.
* 2) We only send as EVALSHA what was sent to the master as EVALSHA, without
* trying to convert EVAL into EVALSHA specifically for slaves.
* 3) Every time we transmit a script as EVAL to the slaves, we also add the
* corresponding SHA1 of the script into the cache as we are sure every
* slave knows about the script starting from now.
* 4) On SCRIPT FLUSH command, we replicate the command to all the slaves
* and at the same time flush the script cache.
* 5) When the last slave disconnects, flush the cache.
* 6) We handle SCRIPT LOAD as well since that's how scripts are loaded
* in the master sometimes.
*/
/* Initialize the script cache, only called at startup. */
void replicationScriptCacheInit(void) {
server.repl_scriptcache_size = 10000;
server.repl_scriptcache_dict = dictCreate(&replScriptCacheDictType);
server.repl_scriptcache_fifo = listCreate();
}
/* Empty the script cache. Should be called every time we are no longer sure
* that every slave knows about all the scripts in our set, or when the
* current AOF "context" is no longer aware of the script. In general we
* should flush the cache:
*
* 1) Every time a new slave reconnects to this master and performs a
* full SYNC (PSYNC does not require flushing).
* 2) Every time an AOF rewrite is performed.
* 3) Every time we are left without slaves at all, and AOF is off, in order
* to reclaim otherwise unused memory.
*/
void replicationScriptCacheFlush(void) {
dictEmpty(server.repl_scriptcache_dict,NULL);
listRelease(server.repl_scriptcache_fifo);
server.repl_scriptcache_fifo = listCreate();
}
/* Add an entry into the script cache, if we reach max number of entries the
* oldest is removed from the list. */
void replicationScriptCacheAdd(sds sha1) {
int retval;
sds key = sdsdup(sha1);
/* Evict oldest. */
if (listLength(server.repl_scriptcache_fifo) == server.repl_scriptcache_size)
{
listNode *ln = listLast(server.repl_scriptcache_fifo);
sds oldest = listNodeValue(ln);
retval = dictDelete(server.repl_scriptcache_dict,oldest);
serverAssert(retval == DICT_OK);
listDelNode(server.repl_scriptcache_fifo,ln);
}
/* Add current. */
retval = dictAdd(server.repl_scriptcache_dict,key,NULL);
listAddNodeHead(server.repl_scriptcache_fifo,key);
serverAssert(retval == DICT_OK);
}
/* Returns non-zero if the specified entry exists inside the cache, that is,
* if all the slaves are aware of this script SHA1. */
int replicationScriptCacheExists(sds sha1) {
return dictFind(server.repl_scriptcache_dict,sha1) != NULL;
}
/* ----------------------- SYNCHRONOUS REPLICATION -------------------------- /* ----------------------- SYNCHRONOUS REPLICATION --------------------------
* Redis synchronous replication design can be summarized in points: * Redis synchronous replication design can be summarized in points:
* *
...@@ -3694,16 +3607,6 @@ void replicationCron(void) { ...@@ -3694,16 +3607,6 @@ void replicationCron(void) {
} }
} }
/* If AOF is disabled and we no longer have attached slaves, we can
* free our Replication Script Cache as there is no need to propagate
* EVALSHA at all. */
if (listLength(server.slaves) == 0 &&
server.aof_state == AOF_OFF &&
listLength(server.repl_scriptcache_fifo) != 0)
{
replicationScriptCacheFlush();
}
replicationStartPendingFork(); replicationStartPendingFork();
/* Remove the RDB file used for replication if Redis is not running /* Remove the RDB file used for replication if Redis is not running
......
...@@ -148,11 +148,10 @@ void scriptResetRun(scriptRunCtx *run_ctx) { ...@@ -148,11 +148,10 @@ void scriptResetRun(scriptRunCtx *run_ctx) {
unprotectClient(run_ctx->original_client); unprotectClient(run_ctx->original_client);
} }
if (!(run_ctx->flags & SCRIPT_EVAL_REPLICATION)) { /* emit EXEC if MULTI has been propagated. */
preventCommandPropagation(run_ctx->original_client); preventCommandPropagation(run_ctx->original_client);
if (run_ctx->flags & SCRIPT_MULTI_EMMITED) { if (run_ctx->flags & SCRIPT_MULTI_EMMITED) {
execCommandPropagateExec(run_ctx->original_client->db->id); execCommandPropagateExec(run_ctx->original_client->db->id);
}
} }
/* unset curr_run_ctx so we will know there is no running script */ /* unset curr_run_ctx so we will know there is no running script */
...@@ -258,17 +257,12 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) { ...@@ -258,17 +257,12 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) {
return C_ERR; return C_ERR;
} }
if ((run_ctx->flags & SCRIPT_RANDOM_DIRTY) && (run_ctx->flags & SCRIPT_EVAL_REPLICATION)) {
*err = sdsnew("Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.");
return C_ERR;
}
/* Write commands are forbidden against read-only slaves, or if a /* Write commands are forbidden against read-only slaves, or if a
* command marked as non-deterministic was already called in the context * command marked as non-deterministic was already called in the context
* of this script. */ * of this script. */
int deny_write_type = writeCommandsDeniedByDiskError(); int deny_write_type = writeCommandsDeniedByDiskError();
if (server.masterhost && server.repl_slave_ro && run_ctx->original_client->flags != CLIENT_ID_AOF if (server.masterhost && server.repl_slave_ro && run_ctx->original_client->id != CLIENT_ID_AOF
&& !(run_ctx->original_client->flags & CLIENT_MASTER)) && !(run_ctx->original_client->flags & CLIENT_MASTER))
{ {
*err = sdsdup(shared.roslaveerr->ptr); *err = sdsdup(shared.roslaveerr->ptr);
...@@ -343,8 +337,7 @@ static void scriptEmitMultiIfNeeded(scriptRunCtx *run_ctx) { ...@@ -343,8 +337,7 @@ static void scriptEmitMultiIfNeeded(scriptRunCtx *run_ctx) {
* we propagate into a MULTI/EXEC block, so that it will be atomic like * we propagate into a MULTI/EXEC block, so that it will be atomic like
* a Lua script in the context of AOF and slaves. */ * a Lua script in the context of AOF and slaves. */
client *c = run_ctx->c; client *c = run_ctx->c;
if (!(run_ctx->flags & SCRIPT_EVAL_REPLICATION) if (!(run_ctx->flags & SCRIPT_MULTI_EMMITED)
&& !(run_ctx->flags & SCRIPT_MULTI_EMMITED)
&& !(run_ctx->original_client->flags & CLIENT_MULTI) && !(run_ctx->original_client->flags & CLIENT_MULTI)
&& (run_ctx->flags & SCRIPT_WRITE_DIRTY) && (run_ctx->flags & SCRIPT_WRITE_DIRTY)
&& ((run_ctx->repl_flags & PROPAGATE_AOF) && ((run_ctx->repl_flags & PROPAGATE_AOF)
...@@ -426,11 +419,6 @@ void scriptCall(scriptRunCtx *run_ctx, robj* *argv, int argc, sds *err) { ...@@ -426,11 +419,6 @@ void scriptCall(scriptRunCtx *run_ctx, robj* *argv, int argc, sds *err) {
run_ctx->flags |= SCRIPT_WRITE_DIRTY; run_ctx->flags |= SCRIPT_WRITE_DIRTY;
} }
if (cmd->flags & CMD_RANDOM) {
/* signify that we already perform a random command in this execution */
run_ctx->flags |= SCRIPT_RANDOM_DIRTY;
}
if (scriptVerifyClusterState(c, run_ctx->original_client, err) != C_OK) { if (scriptVerifyClusterState(c, run_ctx->original_client, err) != C_OK) {
return; return;
} }
...@@ -438,13 +426,11 @@ void scriptCall(scriptRunCtx *run_ctx, robj* *argv, int argc, sds *err) { ...@@ -438,13 +426,11 @@ void scriptCall(scriptRunCtx *run_ctx, robj* *argv, int argc, sds *err) {
scriptEmitMultiIfNeeded(run_ctx); scriptEmitMultiIfNeeded(run_ctx);
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS; int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
if (!(run_ctx->flags & SCRIPT_EVAL_REPLICATION)) { if (run_ctx->repl_flags & PROPAGATE_AOF) {
if (run_ctx->repl_flags & PROPAGATE_AOF) { call_flags |= CMD_CALL_PROPAGATE_AOF;
call_flags |= CMD_CALL_PROPAGATE_AOF; }
} if (run_ctx->repl_flags & PROPAGATE_REPL) {
if (run_ctx->repl_flags & PROPAGATE_REPL) { call_flags |= CMD_CALL_PROPAGATE_REPL;
call_flags |= CMD_CALL_PROPAGATE_REPL;
}
} }
call(c, call_flags); call(c, call_flags);
serverAssert((c->flags & CLIENT_BLOCKED) == 0); serverAssert((c->flags & CLIENT_BLOCKED) == 0);
......
...@@ -59,16 +59,12 @@ ...@@ -59,16 +59,12 @@
/* runCtx flags */ /* runCtx flags */
#define SCRIPT_WRITE_DIRTY (1ULL<<0) /* indicate that the current script already performed a write command */ #define SCRIPT_WRITE_DIRTY (1ULL<<0) /* indicate that the current script already performed a write command */
#define SCRIPT_RANDOM_DIRTY (1ULL<<1) /* indicate that the current script already performed a random reply command.
Thanks to this flag we'll raise an error every time a write command
is called after a random command and prevent none deterministic
replication or AOF. */
#define SCRIPT_MULTI_EMMITED (1ULL<<2) /* indicate that we already wrote a multi command to replication/aof */ #define SCRIPT_MULTI_EMMITED (1ULL<<2) /* indicate that we already wrote a multi command to replication/aof */
#define SCRIPT_TIMEDOUT (1ULL<<3) /* indicate that the current script timedout */ #define SCRIPT_TIMEDOUT (1ULL<<3) /* indicate that the current script timedout */
#define SCRIPT_KILLED (1ULL<<4) /* indicate that the current script was marked to be killed */ #define SCRIPT_KILLED (1ULL<<4) /* indicate that the current script was marked to be killed */
#define SCRIPT_READ_ONLY (1ULL<<5) /* indicate that the current script should only perform read commands */ #define SCRIPT_READ_ONLY (1ULL<<5) /* indicate that the current script should only perform read commands */
#define SCRIPT_EVAL_REPLICATION (1ULL<<6) /* mode for eval, indicate that we replicate the
script invocation and not the effects */
#define SCRIPT_EVAL_MODE (1ULL<<7) /* Indicate that the current script called from legacy Lua */ #define SCRIPT_EVAL_MODE (1ULL<<7) /* Indicate that the current script called from legacy Lua */
typedef struct scriptRunCtx scriptRunCtx; typedef struct scriptRunCtx scriptRunCtx;
......
...@@ -461,36 +461,6 @@ static int luaRaiseError(lua_State *lua) { ...@@ -461,36 +461,6 @@ static int luaRaiseError(lua_State *lua) {
return lua_error(lua); return lua_error(lua);
} }
/* Sort the array currently in the stack. We do this to make the output
* of commands like KEYS or SMEMBERS something deterministic when called
* from Lua (to play well with AOf/replication).
*
* The array is sorted using table.sort itself, and assuming all the
* list elements are strings. */
static void luaSortArray(lua_State *lua) {
/* Initial Stack: array */
lua_getglobal(lua,"table");
lua_pushstring(lua,"sort");
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
if (lua_pcall(lua,1,0,0)) {
/* Stack: array, table, error */
/* We are not interested in the error, we assume that the problem is
* that there are 'false' elements inside the array, so we try
* again with a slower function but able to handle this case, that
* is: table.sort(table, __redis__compare_helper) */
lua_pop(lua,1); /* Stack: array, table */
lua_pushstring(lua,"sort"); /* Stack: array, table, sort */
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
lua_getglobal(lua,"__redis__compare_helper");
/* Stack: array, table, table.sort, array, __redis__compare_helper */
lua_call(lua,2,0);
}
/* Stack: array (sorted), table */
lua_pop(lua,1); /* Stack: array (sorted) */
}
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
* Lua reply to Redis reply conversion functions. * Lua reply to Redis reply conversion functions.
...@@ -826,13 +796,6 @@ static int luaRedisGenericCommand(lua_State *lua, int raise_error) { ...@@ -826,13 +796,6 @@ static int luaRedisGenericCommand(lua_State *lua, int raise_error) {
if (ldbIsEnabled()) if (ldbIsEnabled())
ldbLogRedisReply(reply); ldbLogRedisReply(reply);
/* Sort the output array if needed, assuming it is a non-null multi bulk
* reply as expected. */
if ((c->cmd->flags & CMD_SORT_FOR_SCRIPT) &&
(rctx->flags & SCRIPT_EVAL_REPLICATION) &&
(reply[0] == '*' && reply[1] != '-')) {
luaSortArray(lua);
}
if (reply != c->buf) sdsfree(reply); if (reply != c->buf) sdsfree(reply);
c->reply_bytes = 0; c->reply_bytes = 0;
...@@ -945,17 +908,13 @@ static int luaRedisStatusReplyCommand(lua_State *lua) { ...@@ -945,17 +908,13 @@ static int luaRedisStatusReplyCommand(lua_State *lua) {
* Set the propagation of write commands executed in the context of the * Set the propagation of write commands executed in the context of the
* script to on/off for AOF and slaves. */ * script to on/off for AOF and slaves. */
static int luaRedisSetReplCommand(lua_State *lua) { static int luaRedisSetReplCommand(lua_State *lua) {
int argc = lua_gettop(lua); int flags, argc = lua_gettop(lua);
int flags;
scriptRunCtx* rctx = luaGetFromRegistry(lua, REGISTRY_RUN_CTX_NAME); scriptRunCtx* rctx = luaGetFromRegistry(lua, REGISTRY_RUN_CTX_NAME);
if (rctx->flags & SCRIPT_EVAL_REPLICATION) { if (argc != 1) {
lua_pushstring(lua, "You can set the replication behavior only after turning on single commands replication with redis.replicate_commands()."); lua_pushstring(lua, "redis.set_repl() requires two arguments.");
return lua_error(lua); return lua_error(lua);
} else if (argc != 1) {
lua_pushstring(lua, "redis.set_repl() requires two arguments.");
return lua_error(lua);
} }
flags = lua_tonumber(lua,-1); flags = lua_tonumber(lua,-1);
......
...@@ -2408,12 +2408,11 @@ void initServer(void) { ...@@ -2408,12 +2408,11 @@ void initServer(void) {
} }
if (server.cluster_enabled) clusterInit(); if (server.cluster_enabled) clusterInit();
replicationScriptCacheInit();
scriptingInit(1); scriptingInit(1);
functionsInit(); functionsInit();
slowlogInit(); slowlogInit();
latencyMonitorInit(); latencyMonitorInit();
/* Initialize ACL default password if it exists */ /* Initialize ACL default password if it exists */
ACLUpdateDefaultUserPassword(server.requirepass); ACLUpdateDefaultUserPassword(server.requirepass);
......
...@@ -1749,7 +1749,6 @@ struct redisServer { ...@@ -1749,7 +1749,6 @@ struct redisServer {
/* Scripting */ /* Scripting */
client *script_caller; /* The client running script right now, or NULL */ client *script_caller; /* The client running script right now, or NULL */
mstime_t script_time_limit; /* Script timeout in milliseconds */ mstime_t script_time_limit; /* Script timeout in milliseconds */
int lua_always_replicate_commands; /* Default replication type. */
int script_oom; /* OOM detected when script start */ int script_oom; /* OOM detected when script start */
int script_disable_deny_script; /* Allow running commands marked "no-script" inside a script. */ int script_disable_deny_script; /* Allow running commands marked "no-script" inside a script. */
/* Lazy free */ /* Lazy free */
...@@ -2498,10 +2497,6 @@ void resizeReplicationBacklog(); ...@@ -2498,10 +2497,6 @@ void resizeReplicationBacklog();
void replicationSetMaster(char *ip, int port); void replicationSetMaster(char *ip, int port);
void replicationUnsetMaster(void); void replicationUnsetMaster(void);
void refreshGoodSlavesCount(void); void refreshGoodSlavesCount(void);
void replicationScriptCacheInit(void);
void replicationScriptCacheFlush(void);
void replicationScriptCacheAdd(sds sha1);
int replicationScriptCacheExists(sds sha1);
void processClientsWaitingReplicas(void); void processClientsWaitingReplicas(void);
void unblockClientWaitingReplicas(client *c); void unblockClientWaitingReplicas(client *c);
int replicationCountAcksByOffset(long long offset); int replicationCountAcksByOffset(long long offset);
......
...@@ -485,4 +485,41 @@ tags {"aof external:skip"} { ...@@ -485,4 +485,41 @@ tags {"aof external:skip"} {
catch {exec src/redis-check-aof --truncate-to-timestamp 1628217469 $aof_path} e catch {exec src/redis-check-aof --truncate-to-timestamp 1628217469 $aof_path} e
assert_match {*aborting*} $e assert_match {*aborting*} $e
} }
test {EVAL timeout with slow verbatim Lua script from AOF} {
create_aof {
append_to_aof [formatCommand select 9]
append_to_aof [formatCommand eval {redis.call('set',KEYS[1],'y'); for i=1,1500000 do redis.call('ping') end return 'ok'} 1 x]
}
start_server [list overrides [list dir $server_path appendonly no lua-time-limit 1 aof-use-rdb-preamble no]] {
# generate a long running script that is propagated to the AOF as script
# make sure that the script times out during loading
set rd [redis_deferring_client]
r config set appendonly yes
set start [clock clicks -milliseconds]
$rd debug loadaof
$rd flush
after 100
catch {r ping} err
assert_match {LOADING*} $err
$rd read
set elapsed [expr [clock clicks -milliseconds]-$start]
if {$::verbose} { puts "loading took $elapsed milliseconds" }
$rd close
assert_equal [r get x] y
}
}
test {EVAL can process writes from AOF in read-only replicas} {
create_aof {
append_to_aof [formatCommand select 9]
append_to_aof [formatCommand eval {redis.call("set",KEYS[1],"100")} 1 foo]
append_to_aof [formatCommand eval {redis.call("incr",KEYS[1])} 1 foo]
append_to_aof [formatCommand eval {redis.call("incr",KEYS[1])} 1 foo]
}
start_server [list overrides [list dir $server_path appendonly yes replica-read-only yes replicaof "127.0.0.1 0"]] {
assert_equal [r get foo] 102
}
}
} }
...@@ -371,73 +371,6 @@ start_server {} { ...@@ -371,73 +371,6 @@ start_server {} {
assert {$sync_count == $new_sync_count} assert {$sync_count == $new_sync_count}
} }
test "PSYNC2: Replica RDB restart with EVALSHA in backlog issue #4483" {
# Pick a random slave
set slave_id [expr {($master_id+1)%5}]
set sync_count [status $R($master_id) sync_full]
# Make sure to replicate the first EVAL while the salve is online
# so that it's part of the scripts the master believes it's safe
# to propagate as EVALSHA.
$R($master_id) EVAL {return redis.call("incr","__mycounter")} 0
$R($master_id) EVALSHA e6e0b547500efcec21eddb619ac3724081afee89 0
# Wait for the two to sync
wait_for_condition 50 1000 {
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
} else {
show_cluster_status
fail "Replica not reconnecting"
}
# Prevent the slave from receiving master updates, and at
# the same time send a new script several times to the
# master, so that we'll end with EVALSHA into the backlog.
$R($slave_id) slaveof 127.0.0.1 0
$R($master_id) EVALSHA e6e0b547500efcec21eddb619ac3724081afee89 0
$R($master_id) EVALSHA e6e0b547500efcec21eddb619ac3724081afee89 0
$R($master_id) EVALSHA e6e0b547500efcec21eddb619ac3724081afee89 0
catch {
$R($slave_id) config rewrite
restart_server [expr {0-$slave_id}] true false
set R($slave_id) [srv [expr {0-$slave_id}] client]
}
# Reconfigure the slave correctly again, when it's back online.
set retry 50
while {$retry} {
if {[catch {
$R($slave_id) slaveof $master_host $master_port
}]} {
after 1000
} else {
break
}
incr retry -1
}
# The master should be back at 4 slaves eventually
wait_for_condition 50 1000 {
[status $R($master_id) connected_slaves] == 4
} else {
show_cluster_status
fail "Replica not reconnecting"
}
set new_sync_count [status $R($master_id) sync_full]
assert {$sync_count == $new_sync_count}
# However if the slave started with the full state of the
# scripting engine, we should now have the same digest.
wait_for_condition 50 1000 {
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
} else {
show_cluster_status
fail "Debug digest mismatch between master and replica in post-restart handshake"
}
}
if {$no_exit} { if {$no_exit} {
while 1 { puts -nonewline .; flush stdout; after 1000} while 1 { puts -nonewline .; flush stdout; after 1000}
} }
......
...@@ -355,4 +355,13 @@ start_server {overrides {save ""}} { ...@@ -355,4 +355,13 @@ start_server {overrides {save ""}} {
} }
} ;# system_name } ;# system_name
exec cp -f tests/assets/scriptbackup.rdb $server_path
start_server [list overrides [list "dir" $server_path "dbfilename" "scriptbackup.rdb" "appendonly" "no"]] {
# the script is: "return redis.call('set', 'foo', 'bar')""
# its sha1 is: a0c38691e9fffe4563723c32ba77a34398e090e6
test {script won't load anymore if it's in rdb} {
assert_equal [r script exists a0c38691e9fffe4563723c32ba77a34398e090e6] 0
}
}
} ;# tags } ;# tags
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment