
We now no longer propagate scripts (started from 7.0), so this is a very rare issue that in nearly-dead-code. This is an overlook in #9780
579 lines
22 KiB
C
579 lines
22 KiB
C
/*
|
|
* Copyright (c) 2009-2021, Redis Ltd.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
* to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "server.h"
|
|
#include "script.h"
|
|
#include "cluster.h"
|
|
|
|
scriptFlag scripts_flags_def[] = {
|
|
{.flag = SCRIPT_FLAG_NO_WRITES, .str = "no-writes"},
|
|
{.flag = SCRIPT_FLAG_ALLOW_OOM, .str = "allow-oom"},
|
|
{.flag = SCRIPT_FLAG_ALLOW_STALE, .str = "allow-stale"},
|
|
{.flag = SCRIPT_FLAG_NO_CLUSTER, .str = "no-cluster"},
|
|
{.flag = SCRIPT_FLAG_ALLOW_CROSS_SLOT, .str = "allow-cross-slot-keys"},
|
|
{.flag = 0, .str = NULL}, /* flags array end */
|
|
};
|
|
|
|
/* On script invocation, holding the current run context */
|
|
static scriptRunCtx *curr_run_ctx = NULL;
|
|
|
|
static void exitScriptTimedoutMode(scriptRunCtx *run_ctx) {
|
|
serverAssert(run_ctx == curr_run_ctx);
|
|
serverAssert(scriptIsTimedout());
|
|
run_ctx->flags &= ~SCRIPT_TIMEDOUT;
|
|
blockingOperationEnds();
|
|
/* if we are a replica and we have an active master, set it for continue processing */
|
|
if (server.masterhost && server.master) queueClientForReprocessing(server.master);
|
|
}
|
|
|
|
static void enterScriptTimedoutMode(scriptRunCtx *run_ctx) {
|
|
serverAssert(run_ctx == curr_run_ctx);
|
|
serverAssert(!scriptIsTimedout());
|
|
/* Mark script as timedout */
|
|
run_ctx->flags |= SCRIPT_TIMEDOUT;
|
|
blockingOperationStarts();
|
|
}
|
|
|
|
int scriptIsTimedout(void) {
|
|
return scriptIsRunning() && (curr_run_ctx->flags & SCRIPT_TIMEDOUT);
|
|
}
|
|
|
|
client* scriptGetClient(void) {
|
|
serverAssert(scriptIsRunning());
|
|
return curr_run_ctx->c;
|
|
}
|
|
|
|
client* scriptGetCaller(void) {
|
|
serverAssert(scriptIsRunning());
|
|
return curr_run_ctx->original_client;
|
|
}
|
|
|
|
/* interrupt function for scripts, should be call
|
|
* from time to time to reply some special command (like ping)
|
|
* and also check if the run should be terminated. */
|
|
int scriptInterrupt(scriptRunCtx *run_ctx) {
|
|
if (run_ctx->flags & SCRIPT_TIMEDOUT) {
|
|
/* script already timedout
|
|
we just need to precess some events and return */
|
|
processEventsWhileBlocked();
|
|
return (run_ctx->flags & SCRIPT_KILLED) ? SCRIPT_KILL : SCRIPT_CONTINUE;
|
|
}
|
|
|
|
long long elapsed = elapsedMs(run_ctx->start_time);
|
|
if (elapsed < server.busy_reply_threshold) {
|
|
return SCRIPT_CONTINUE;
|
|
}
|
|
|
|
serverLog(LL_WARNING,
|
|
"Slow script detected: still in execution after %lld milliseconds. "
|
|
"You can try killing the script using the %s command. Script name is: %s.",
|
|
elapsed, (run_ctx->flags & SCRIPT_EVAL_MODE) ? "SCRIPT KILL" : "FUNCTION KILL", run_ctx->funcname);
|
|
|
|
enterScriptTimedoutMode(run_ctx);
|
|
/* Once the script timeouts we reenter the event loop to permit others
|
|
* some commands execution. For this reason
|
|
* we need to mask the client executing the script from the event loop.
|
|
* If we don't do that the client may disconnect and could no longer be
|
|
* here when the EVAL command will return. */
|
|
protectClient(run_ctx->original_client);
|
|
|
|
processEventsWhileBlocked();
|
|
|
|
return (run_ctx->flags & SCRIPT_KILLED) ? SCRIPT_KILL : SCRIPT_CONTINUE;
|
|
}
|
|
|
|
uint64_t scriptFlagsToCmdFlags(uint64_t cmd_flags, uint64_t script_flags) {
|
|
/* If the script declared flags, clear the ones from the command and use the ones it declared.*/
|
|
cmd_flags &= ~(CMD_STALE | CMD_DENYOOM | CMD_WRITE);
|
|
|
|
/* NO_WRITES implies ALLOW_OOM */
|
|
if (!(script_flags & (SCRIPT_FLAG_ALLOW_OOM | SCRIPT_FLAG_NO_WRITES)))
|
|
cmd_flags |= CMD_DENYOOM;
|
|
if (!(script_flags & SCRIPT_FLAG_NO_WRITES))
|
|
cmd_flags |= CMD_WRITE;
|
|
if (script_flags & SCRIPT_FLAG_ALLOW_STALE)
|
|
cmd_flags |= CMD_STALE;
|
|
|
|
/* In addition the MAY_REPLICATE flag is set for these commands, but
|
|
* if we have flags we know if it's gonna do any writes or not. */
|
|
cmd_flags &= ~CMD_MAY_REPLICATE;
|
|
|
|
return cmd_flags;
|
|
}
|
|
|
|
/* Prepare the given run ctx for execution */
|
|
int scriptPrepareForRun(scriptRunCtx *run_ctx, client *engine_client, client *caller, const char *funcname, uint64_t script_flags, int ro) {
|
|
serverAssert(!curr_run_ctx);
|
|
int client_allow_oom = !!(caller->flags & CLIENT_ALLOW_OOM);
|
|
|
|
int running_stale = server.masterhost &&
|
|
server.repl_state != REPL_STATE_CONNECTED &&
|
|
server.repl_serve_stale_data == 0;
|
|
int obey_client = mustObeyClient(caller);
|
|
|
|
if (!(script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE)) {
|
|
if ((script_flags & SCRIPT_FLAG_NO_CLUSTER) && server.cluster_enabled) {
|
|
addReplyError(caller, "Can not run script on cluster, 'no-cluster' flag is set.");
|
|
return C_ERR;
|
|
}
|
|
|
|
if (running_stale && !(script_flags & SCRIPT_FLAG_ALLOW_STALE)) {
|
|
addReplyError(caller, "-MASTERDOWN Link with MASTER is down, "
|
|
"replica-serve-stale-data is set to 'no' "
|
|
"and 'allow-stale' flag is not set on the script.");
|
|
return C_ERR;
|
|
}
|
|
|
|
if (!(script_flags & SCRIPT_FLAG_NO_WRITES)) {
|
|
/* Script may perform writes we need to verify:
|
|
* 1. we are not a readonly replica
|
|
* 2. no disk error detected
|
|
* 3. command is not `fcall_ro`/`eval[sha]_ro` */
|
|
if (server.masterhost && server.repl_slave_ro && !obey_client) {
|
|
addReplyError(caller, "-READONLY Can not run script with write flag on readonly replica");
|
|
return C_ERR;
|
|
}
|
|
|
|
/* Deny writes if we're unable to persist. */
|
|
int deny_write_type = writeCommandsDeniedByDiskError();
|
|
if (deny_write_type != DISK_ERROR_TYPE_NONE && !obey_client) {
|
|
if (deny_write_type == DISK_ERROR_TYPE_RDB)
|
|
addReplyError(caller, "-MISCONF Redis is configured to save RDB snapshots, "
|
|
"but it's currently unable to persist to disk. "
|
|
"Writable scripts are blocked. Use 'no-writes' flag for read only scripts.");
|
|
else
|
|
addReplyErrorFormat(caller, "-MISCONF Redis is configured to persist data to AOF, "
|
|
"but it's currently unable to persist to disk. "
|
|
"Writable scripts are blocked. Use 'no-writes' flag for read only scripts. "
|
|
"AOF error: %s", strerror(server.aof_last_write_errno));
|
|
return C_ERR;
|
|
}
|
|
|
|
if (ro) {
|
|
addReplyError(caller, "Can not execute a script with write flag using *_ro command.");
|
|
return C_ERR;
|
|
}
|
|
|
|
/* Don't accept write commands if there are not enough good slaves and
|
|
* user configured the min-slaves-to-write option. */
|
|
if (!checkGoodReplicasStatus()) {
|
|
addReplyErrorObject(caller, shared.noreplicaserr);
|
|
return C_ERR;
|
|
}
|
|
}
|
|
|
|
/* Check OOM state. the no-writes flag imply allow-oom. we tested it
|
|
* after the no-write error, so no need to mention it in the error reply. */
|
|
if (!client_allow_oom && server.pre_command_oom_state && server.maxmemory &&
|
|
!(script_flags & (SCRIPT_FLAG_ALLOW_OOM|SCRIPT_FLAG_NO_WRITES)))
|
|
{
|
|
addReplyError(caller, "-OOM allow-oom flag is not set on the script, "
|
|
"can not run it when used memory > 'maxmemory'");
|
|
return C_ERR;
|
|
}
|
|
|
|
} else {
|
|
/* Special handling for backwards compatibility (no shebang eval[sha]) mode */
|
|
if (running_stale) {
|
|
addReplyErrorObject(caller, shared.masterdownerr);
|
|
return C_ERR;
|
|
}
|
|
}
|
|
|
|
run_ctx->c = engine_client;
|
|
run_ctx->original_client = caller;
|
|
run_ctx->funcname = funcname;
|
|
|
|
client *script_client = run_ctx->c;
|
|
client *curr_client = run_ctx->original_client;
|
|
|
|
/* Select the right DB in the context of the Lua client */
|
|
selectDb(script_client, curr_client->db->id);
|
|
script_client->resp = 2; /* Default is RESP2, scripts can change it. */
|
|
|
|
/* If we are in MULTI context, flag Lua client as CLIENT_MULTI. */
|
|
if (curr_client->flags & CLIENT_MULTI) {
|
|
script_client->flags |= CLIENT_MULTI;
|
|
}
|
|
|
|
run_ctx->start_time = getMonotonicUs();
|
|
|
|
run_ctx->flags = 0;
|
|
run_ctx->repl_flags = PROPAGATE_AOF | PROPAGATE_REPL;
|
|
|
|
if (ro || (!(script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE) && (script_flags & SCRIPT_FLAG_NO_WRITES))) {
|
|
/* On fcall_ro or on functions that do not have the 'write'
|
|
* flag, we will not allow write commands. */
|
|
run_ctx->flags |= SCRIPT_READ_ONLY;
|
|
}
|
|
if (client_allow_oom || (!(script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE) && (script_flags & SCRIPT_FLAG_ALLOW_OOM))) {
|
|
/* Note: we don't need to test the no-writes flag here and set this run_ctx flag,
|
|
* since only write commands can are deny-oom. */
|
|
run_ctx->flags |= SCRIPT_ALLOW_OOM;
|
|
}
|
|
|
|
if ((script_flags & SCRIPT_FLAG_EVAL_COMPAT_MODE) || (script_flags & SCRIPT_FLAG_ALLOW_CROSS_SLOT)) {
|
|
run_ctx->flags |= SCRIPT_ALLOW_CROSS_SLOT;
|
|
}
|
|
|
|
/* set the curr_run_ctx so we can use it to kill the script if needed */
|
|
curr_run_ctx = run_ctx;
|
|
|
|
return C_OK;
|
|
}
|
|
|
|
/* Reset the given run ctx after execution */
|
|
void scriptResetRun(scriptRunCtx *run_ctx) {
|
|
serverAssert(curr_run_ctx);
|
|
|
|
/* After the script done, remove the MULTI state. */
|
|
run_ctx->c->flags &= ~CLIENT_MULTI;
|
|
|
|
if (scriptIsTimedout()) {
|
|
exitScriptTimedoutMode(run_ctx);
|
|
/* Restore the client that was protected when the script timeout
|
|
* was detected. */
|
|
unprotectClient(run_ctx->original_client);
|
|
}
|
|
|
|
preventCommandPropagation(run_ctx->original_client);
|
|
|
|
/* unset curr_run_ctx so we will know there is no running script */
|
|
curr_run_ctx = NULL;
|
|
}
|
|
|
|
/* return true if a script is currently running */
|
|
int scriptIsRunning(void) {
|
|
return curr_run_ctx != NULL;
|
|
}
|
|
|
|
const char* scriptCurrFunction(void) {
|
|
serverAssert(scriptIsRunning());
|
|
return curr_run_ctx->funcname;
|
|
}
|
|
|
|
int scriptIsEval(void) {
|
|
serverAssert(scriptIsRunning());
|
|
return curr_run_ctx->flags & SCRIPT_EVAL_MODE;
|
|
}
|
|
|
|
/* Kill the current running script */
|
|
void scriptKill(client *c, int is_eval) {
|
|
if (!curr_run_ctx) {
|
|
addReplyError(c, "-NOTBUSY No scripts in execution right now.");
|
|
return;
|
|
}
|
|
if (mustObeyClient(curr_run_ctx->original_client)) {
|
|
addReplyError(c,
|
|
"-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.");
|
|
return;
|
|
}
|
|
if (curr_run_ctx->flags & SCRIPT_WRITE_DIRTY) {
|
|
addReplyError(c,
|
|
"-UNKILLABLE Sorry the script already executed write "
|
|
"commands against the dataset. You can either wait the "
|
|
"script termination or kill the server in a hard way "
|
|
"using the SHUTDOWN NOSAVE command.");
|
|
return;
|
|
}
|
|
if (is_eval && !(curr_run_ctx->flags & SCRIPT_EVAL_MODE)) {
|
|
/* Kill a function with 'SCRIPT KILL' is not allow */
|
|
addReplyErrorObject(c, shared.slowscripterr);
|
|
return;
|
|
}
|
|
if (!is_eval && (curr_run_ctx->flags & SCRIPT_EVAL_MODE)) {
|
|
/* Kill an eval with 'FUNCTION KILL' is not allow */
|
|
addReplyErrorObject(c, shared.slowevalerr);
|
|
return;
|
|
}
|
|
curr_run_ctx->flags |= SCRIPT_KILLED;
|
|
addReply(c, shared.ok);
|
|
}
|
|
|
|
static int scriptVerifyCommandArity(struct redisCommand *cmd, int argc, sds *err) {
|
|
if (!cmd || ((cmd->arity > 0 && cmd->arity != argc) || (argc < -cmd->arity))) {
|
|
if (cmd)
|
|
*err = sdsnew("Wrong number of args calling Redis command from script");
|
|
else
|
|
*err = sdsnew("Unknown Redis command called from script");
|
|
return C_ERR;
|
|
}
|
|
return C_OK;
|
|
}
|
|
|
|
static int scriptVerifyACL(client *c, sds *err) {
|
|
/* Check the ACLs. */
|
|
int acl_errpos;
|
|
int acl_retval = ACLCheckAllPerm(c, &acl_errpos);
|
|
if (acl_retval != ACL_OK) {
|
|
addACLLogEntry(c,acl_retval,ACL_LOG_CTX_LUA,acl_errpos,NULL,NULL);
|
|
sds msg = getAclErrorMessage(acl_retval, c->user, c->cmd, c->argv[acl_errpos]->ptr, 0);
|
|
*err = sdscatsds(sdsnew("ACL failure in script: "), msg);
|
|
sdsfree(msg);
|
|
return C_ERR;
|
|
}
|
|
return C_OK;
|
|
}
|
|
|
|
static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) {
|
|
|
|
/* A write command, on an RO command or an RO script is rejected ASAP.
|
|
* Note: For scripts, we consider may-replicate commands as write commands.
|
|
* This also makes it possible to allow read-only scripts to be run during
|
|
* CLIENT PAUSE WRITE. */
|
|
if (run_ctx->flags & SCRIPT_READ_ONLY &&
|
|
(run_ctx->c->cmd->flags & (CMD_WRITE|CMD_MAY_REPLICATE)))
|
|
{
|
|
*err = sdsnew("Write commands are not allowed from read-only scripts.");
|
|
return C_ERR;
|
|
}
|
|
|
|
/* The other checks below are on the server state and are only relevant for
|
|
* write commands, return if this is not a write command. */
|
|
if (!(run_ctx->c->cmd->flags & CMD_WRITE))
|
|
return C_OK;
|
|
|
|
/* If the script already made a modification to the dataset, we can't
|
|
* fail it on unpredictable error state. */
|
|
if ((run_ctx->flags & SCRIPT_WRITE_DIRTY))
|
|
return C_OK;
|
|
|
|
/* Write commands are forbidden against read-only slaves, or if a
|
|
* command marked as non-deterministic was already called in the context
|
|
* of this script. */
|
|
int deny_write_type = writeCommandsDeniedByDiskError();
|
|
|
|
if (server.masterhost && server.repl_slave_ro &&
|
|
!mustObeyClient(run_ctx->original_client))
|
|
{
|
|
*err = sdsdup(shared.roslaveerr->ptr);
|
|
return C_ERR;
|
|
}
|
|
|
|
if (deny_write_type != DISK_ERROR_TYPE_NONE) {
|
|
*err = writeCommandsGetDiskErrorMessage(deny_write_type);
|
|
return C_ERR;
|
|
}
|
|
|
|
/* Don't accept write commands if there are not enough good slaves and
|
|
* user configured the min-slaves-to-write option. Note this only reachable
|
|
* for Eval scripts that didn't declare flags, see the other check in
|
|
* scriptPrepareForRun */
|
|
if (!checkGoodReplicasStatus()) {
|
|
*err = sdsdup(shared.noreplicaserr->ptr);
|
|
return C_ERR;
|
|
}
|
|
|
|
return C_OK;
|
|
}
|
|
|
|
static int scriptVerifyOOM(scriptRunCtx *run_ctx, char **err) {
|
|
if (run_ctx->flags & SCRIPT_ALLOW_OOM) {
|
|
/* Allow running any command even if OOM reached */
|
|
return C_OK;
|
|
}
|
|
|
|
/* If we reached the memory limit configured via maxmemory, commands that
|
|
* could enlarge the memory usage are not allowed, but only if this is the
|
|
* first write in the context of this script, otherwise we can't stop
|
|
* in the middle. */
|
|
|
|
if (server.maxmemory && /* Maxmemory is actually enabled. */
|
|
!mustObeyClient(run_ctx->original_client) && /* Don't care about mem for replicas or AOF. */
|
|
!(run_ctx->flags & SCRIPT_WRITE_DIRTY) && /* Script had no side effects so far. */
|
|
server.pre_command_oom_state && /* Detected OOM when script start. */
|
|
(run_ctx->c->cmd->flags & CMD_DENYOOM))
|
|
{
|
|
*err = sdsdup(shared.oomerr->ptr);
|
|
return C_ERR;
|
|
}
|
|
|
|
return C_OK;
|
|
}
|
|
|
|
static int scriptVerifyClusterState(scriptRunCtx *run_ctx, client *c, client *original_c, sds *err) {
|
|
if (!server.cluster_enabled || mustObeyClient(original_c)) {
|
|
return C_OK;
|
|
}
|
|
/* If this is a Redis Cluster node, we need to make sure the script is not
|
|
* trying to access non-local keys, with the exception of commands
|
|
* received from our master or when loading the AOF back in memory. */
|
|
int error_code;
|
|
/* Duplicate relevant flags in the script client. */
|
|
c->flags &= ~(CLIENT_READONLY | CLIENT_ASKING);
|
|
c->flags |= original_c->flags & (CLIENT_READONLY | CLIENT_ASKING);
|
|
int hashslot = -1;
|
|
if (getNodeByQuery(c, c->cmd, c->argv, c->argc, &hashslot, &error_code) != server.cluster->myself) {
|
|
if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
|
|
*err = sdsnew(
|
|
"Script attempted to execute a write command while the "
|
|
"cluster is down and readonly");
|
|
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
|
|
*err = sdsnew("Script attempted to execute a command while the "
|
|
"cluster is down");
|
|
} else {
|
|
*err = sdsnew("Script attempted to access a non local key in a "
|
|
"cluster node");
|
|
}
|
|
return C_ERR;
|
|
}
|
|
|
|
/* If the script declared keys in advanced, the cross slot error would have
|
|
* already been thrown. This is only checking for cross slot keys being accessed
|
|
* that weren't pre-declared. */
|
|
if (hashslot != -1 && !(run_ctx->flags & SCRIPT_ALLOW_CROSS_SLOT)) {
|
|
if (original_c->slot == -1) {
|
|
original_c->slot = hashslot;
|
|
} else if (original_c->slot != hashslot) {
|
|
*err = sdsnew("Script attempted to access keys that do not hash to "
|
|
"the same slot");
|
|
return C_ERR;
|
|
}
|
|
}
|
|
return C_OK;
|
|
}
|
|
|
|
/* set RESP for a given run_ctx */
|
|
int scriptSetResp(scriptRunCtx *run_ctx, int resp) {
|
|
if (resp != 2 && resp != 3) {
|
|
return C_ERR;
|
|
}
|
|
|
|
run_ctx->c->resp = resp;
|
|
return C_OK;
|
|
}
|
|
|
|
/* set Repl for a given run_ctx
|
|
* either: PROPAGATE_AOF | PROPAGATE_REPL*/
|
|
int scriptSetRepl(scriptRunCtx *run_ctx, int repl) {
|
|
if ((repl & ~(PROPAGATE_AOF | PROPAGATE_REPL)) != 0) {
|
|
return C_ERR;
|
|
}
|
|
run_ctx->repl_flags = repl;
|
|
return C_OK;
|
|
}
|
|
|
|
static int scriptVerifyAllowStale(client *c, sds *err) {
|
|
if (!server.masterhost) {
|
|
/* Not a replica, stale is irrelevant */
|
|
return C_OK;
|
|
}
|
|
|
|
if (server.repl_state == REPL_STATE_CONNECTED) {
|
|
/* Connected to replica, stale is irrelevant */
|
|
return C_OK;
|
|
}
|
|
|
|
if (server.repl_serve_stale_data == 1) {
|
|
/* Disconnected from replica but allow to serve data */
|
|
return C_OK;
|
|
}
|
|
|
|
if (c->cmd->flags & CMD_STALE) {
|
|
/* Command is allow while stale */
|
|
return C_OK;
|
|
}
|
|
|
|
/* On stale replica, can not run the command */
|
|
*err = sdsnew("Can not execute the command on a stale replica");
|
|
return C_ERR;
|
|
}
|
|
|
|
/* Call a Redis command.
|
|
* The reply is written to the run_ctx client and it is
|
|
* up to the engine to take and parse.
|
|
* The err out variable is set only if error occurs and describe the error.
|
|
* If err is set on reply is written to the run_ctx client. */
|
|
void scriptCall(scriptRunCtx *run_ctx, sds *err) {
|
|
client *c = run_ctx->c;
|
|
|
|
/* Setup our fake client for command execution */
|
|
c->user = run_ctx->original_client->user;
|
|
|
|
/* Process module hooks */
|
|
moduleCallCommandFilters(c);
|
|
|
|
struct redisCommand *cmd = lookupCommand(c->argv, c->argc);
|
|
c->cmd = c->lastcmd = c->realcmd = cmd;
|
|
if (scriptVerifyCommandArity(cmd, c->argc, err) != C_OK) {
|
|
goto error;
|
|
}
|
|
|
|
/* There are commands that are not allowed inside scripts. */
|
|
if (!server.script_disable_deny_script && (cmd->flags & CMD_NOSCRIPT)) {
|
|
*err = sdsnew("This Redis command is not allowed from script");
|
|
goto error;
|
|
}
|
|
|
|
if (scriptVerifyAllowStale(c, err) != C_OK) {
|
|
goto error;
|
|
}
|
|
|
|
if (scriptVerifyACL(c, err) != C_OK) {
|
|
goto error;
|
|
}
|
|
|
|
if (scriptVerifyWriteCommandAllow(run_ctx, err) != C_OK) {
|
|
goto error;
|
|
}
|
|
|
|
if (scriptVerifyOOM(run_ctx, err) != C_OK) {
|
|
goto error;
|
|
}
|
|
|
|
if (cmd->flags & CMD_WRITE) {
|
|
/* signify that we already change the data in this execution */
|
|
run_ctx->flags |= SCRIPT_WRITE_DIRTY;
|
|
}
|
|
|
|
if (scriptVerifyClusterState(run_ctx, c, run_ctx->original_client, err) != C_OK) {
|
|
goto error;
|
|
}
|
|
|
|
int call_flags = CMD_CALL_NONE;
|
|
if (run_ctx->repl_flags & PROPAGATE_AOF) {
|
|
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
|
}
|
|
if (run_ctx->repl_flags & PROPAGATE_REPL) {
|
|
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
|
}
|
|
call(c, call_flags);
|
|
serverAssert((c->flags & CLIENT_BLOCKED) == 0);
|
|
return;
|
|
|
|
error:
|
|
afterErrorReply(c, *err, sdslen(*err), 0);
|
|
incrCommandStatsOnError(cmd, ERROR_COMMAND_REJECTED);
|
|
}
|
|
|
|
long long scriptRunDuration(void) {
|
|
serverAssert(scriptIsRunning());
|
|
return elapsedMs(curr_run_ctx->start_time);
|
|
}
|