summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEduardo Semprebon <eduardobr@gmail.com>2022-04-26 13:34:04 +0200
committerGitHub <noreply@github.com>2022-04-26 14:34:04 +0300
commit3a1d14259d5a1cec90a5afa50a49e4d9d881ab37 (patch)
tree14b7aceb14bf0f4f8185276ac4891fd2c98f3630
parent156836bfe500a2dce9319fdba259d40735421a38 (diff)
downloadredis-3a1d14259d5a1cec90a5afa50a49e4d9d881ab37.tar.gz
Allow configuring signaled shutdown flags (#10594)
The SHUTDOWN command has various flags to change it's default behavior, but in some cases establishing a connection to redis is complicated and it's easier for the management software to use signals. however, so far the signals could only trigger the default shutdown behavior. Here we introduce the option to control shutdown arguments for SIGTERM and SIGINT. New config options: `shutdown-on-sigint [nosave | save] [now] [force]` `shutdown-on-sigterm [nosave | save] [now] [force]` Implementation: Support MULTI_ARG_CONFIG on createEnumConfig to support multiple enums to be applied as bit flags. Co-authored-by: Oran Agra <oran@redislabs.com>
-rw-r--r--redis.conf16
-rw-r--r--src/config.c100
-rw-r--r--src/server.c14
-rw-r--r--src/server.h3
-rw-r--r--tests/modules/moduleconfigs.c2
-rw-r--r--tests/unit/introspection.tcl8
-rw-r--r--tests/unit/moduleapi/moduleconfigs.tcl2
7 files changed, 111 insertions, 34 deletions
diff --git a/redis.conf b/redis.conf
index 62062b397..7635c14b4 100644
--- a/redis.conf
+++ b/redis.conf
@@ -1527,6 +1527,22 @@ aof-timestamp-enabled no
#
# shutdown-timeout 10
+# When Redis receives a SIGINT or SIGTERM, shutdown is initiated and by default
+# an RDB snapshot is written to disk in a blocking operation if save points are configured.
+# The options used on signaled shutdown can include the following values:
+# default: Saves RDB snapshot only if save points are configured.
+# Waits for lagging replicas to catch up.
+# save: Forces a DB saving operation even if no save points are configured.
+# nosave: Prevents DB saving operation even if one or more save points are configured.
+# now: Skips waiting for lagging replicas.
+# force: Ignores any errors that would normally prevent the server from exiting.
+#
+# Any combination of values is allowed as long as "save" and "nosave" are not set simultaneously.
+# Example: "nosave force now"
+#
+# shutdown-on-sigint default
+# shutdown-on-sigterm default
+
################ NON-DETERMINISTIC LONG BLOCKING COMMANDS #####################
# Maximum time in milliseconds for EVAL scripts, functions and in some cases
diff --git a/src/config.c b/src/config.c
index 61841cb62..805aececb 100644
--- a/src/config.c
+++ b/src/config.c
@@ -94,6 +94,15 @@ configEnum aof_fsync_enum[] = {
{NULL, 0}
};
+configEnum shutdown_on_sig_enum[] = {
+ {"default", 0},
+ {"save", SHUTDOWN_SAVE},
+ {"nosave", SHUTDOWN_NOSAVE},
+ {"now", SHUTDOWN_NOW},
+ {"force", SHUTDOWN_FORCE},
+ {NULL, 0}
+};
+
configEnum repl_diskless_load_enum[] = {
{"disabled", REPL_DISKLESS_LOAD_DISABLED},
{"on-empty-db", REPL_DISKLESS_LOAD_WHEN_DB_EMPTY},
@@ -283,33 +292,50 @@ static standardConfig *lookupConfig(sds name) {
*----------------------------------------------------------------------------*/
/* Get enum value from name. If there is no match INT_MIN is returned. */
-int configEnumGetValue(configEnum *ce, char *name) {
- while(ce->name != NULL) {
- if (!strcasecmp(ce->name,name)) return ce->val;
- ce++;
+int configEnumGetValue(configEnum *ce, sds *argv, int argc, int bitflags) {
+ if (argc == 0 || (!bitflags && argc != 1)) return INT_MIN;
+ int values = 0;
+ for (int i = 0; i < argc; i++) {
+ int matched = 0;
+ for (configEnum *ceItem = ce; ceItem->name != NULL; ceItem++) {
+ if (!strcasecmp(argv[i],ceItem->name)) {
+ values |= ceItem->val;
+ matched = 1;
+ }
+ }
+ if (!matched) return INT_MIN;
}
- return INT_MIN;
+ return values;
}
-/* Get enum name from value. If no match is found NULL is returned. */
-const char *configEnumGetName(configEnum *ce, int val) {
- while(ce->name != NULL) {
- if (ce->val == val) return ce->name;
- ce++;
+/* Get enum name/s from value. If no matches are found "unknown" is returned. */
+static sds configEnumGetName(configEnum *ce, int values, int bitflags) {
+ sds names = NULL;
+ int matches = 0;
+ for( ; ce->name != NULL; ce++) {
+ if (values == ce->val) { /* Short path for perfect match */
+ sdsfree(names);
+ return sdsnew(ce->name);
+ }
+ if (bitflags && (values & ce->val)) {
+ names = names ? sdscatfmt(names, " %s", ce->name) : sdsnew(ce->name);
+ matches |= ce->val;
+ }
}
- return NULL;
-}
-
-/* Wrapper for configEnumGetName() returning "unknown" instead of NULL if
- * there is no match. */
-const char *configEnumGetNameOrUnknown(configEnum *ce, int val) {
- const char *name = configEnumGetName(ce,val);
- return name ? name : "unknown";
+ if (!names || values != matches) {
+ sdsfree(names);
+ return sdsnew("unknown");
+ }
+ return names;
}
/* Used for INFO generation. */
const char *evictPolicyToString(void) {
- return configEnumGetNameOrUnknown(maxmemory_policy_enum,server.maxmemory_policy);
+ for (configEnum *ce = maxmemory_policy_enum; ce->name != NULL; ce++) {
+ if (server.maxmemory_policy == ce->val)
+ return ce->name;
+ }
+ serverPanic("unknown eviction policy");
}
/*-----------------------------------------------------------------------------
@@ -1304,12 +1330,13 @@ void rewriteConfigOctalOption(struct rewriteConfigState *state, const char *opti
/* Rewrite an enumeration option. It takes as usually state and option name,
* and in addition the enumeration array and the default value for the
* option. */
-void rewriteConfigEnumOption(struct rewriteConfigState *state, const char *option, int value, configEnum *ce, int defval) {
- sds line;
- const char *name = configEnumGetNameOrUnknown(ce,value);
- int force = value != defval;
+void rewriteConfigEnumOption(struct rewriteConfigState *state, const char *option, int value, standardConfig *config) {
+ int multiarg = config->flags & MULTI_ARG_CONFIG;
+ sds names = configEnumGetName(config->data.enumd.enum_value,value,multiarg);
+ sds line = sdscatfmt(sdsempty(),"%s %s",option,names);
+ sdsfree(names);
+ int force = value != config->data.enumd.default_value;
- line = sdscatprintf(sdsempty(),"%s %s",option,name);
rewriteConfigRewriteLine(state,option,line,force);
}
@@ -1898,10 +1925,12 @@ static void enumConfigInit(standardConfig *config) {
}
static int enumConfigSet(standardConfig *config, sds *argv, int argc, const char **err) {
- UNUSED(argc);
- int enumval = configEnumGetValue(config->data.enumd.enum_value, argv[0]);
+ int enumval;
+ int bitflags = !!(config->flags & MULTI_ARG_CONFIG);
+ enumval = configEnumGetValue(config->data.enumd.enum_value, argv, argc, bitflags);
+
if (enumval == INT_MIN) {
- sds enumerr = sdsnew("argument must be one of the following: ");
+ sds enumerr = sdsnew("argument(s) must be one of the following: ");
configEnum *enumNode = config->data.enumd.enum_value;
while(enumNode->name != NULL) {
enumerr = sdscatlen(enumerr, enumNode->name,
@@ -1932,12 +1961,13 @@ static int enumConfigSet(standardConfig *config, sds *argv, int argc, const char
static sds enumConfigGet(standardConfig *config) {
int val = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config);
- return sdsnew(configEnumGetNameOrUnknown(config->data.enumd.enum_value,val));
+ int bitflags = !!(config->flags & MULTI_ARG_CONFIG);
+ return configEnumGetName(config->data.enumd.enum_value,val,bitflags);
}
static void enumConfigRewrite(standardConfig *config, const char *name, struct rewriteConfigState *state) {
int val = config->flags & MODULE_CONFIG ? getModuleEnumConfig(config->privdata) : *(config->data.enumd.config);
- rewriteConfigEnumOption(state, name, val, config->data.enumd.enum_value, config->data.enumd.default_value);
+ rewriteConfigEnumOption(state, name, val, config);
}
#define createEnumConfig(name, alias, flags, enum, config_addr, default, is_valid, apply) { \
@@ -2297,6 +2327,16 @@ static int isValidAOFdirname(char *val, const char **err) {
return 1;
}
+static int isValidShutdownOnSigFlags(int val, const char **err) {
+ /* Individual arguments are validated by createEnumConfig logic.
+ * We just need to ensure valid combinations here. */
+ if (val & SHUTDOWN_NOSAVE && val & SHUTDOWN_SAVE) {
+ *err = "shutdown options SAVE and NOSAVE can't be used simultaneously";
+ return 0;
+ }
+ return 1;
+}
+
static int isValidAnnouncedHostname(char *val, const char **err) {
if (strlen(val) >= NET_HOST_STR_LEN) {
*err = "Hostnames must be less than "
@@ -2943,6 +2983,8 @@ standardConfig static_configs[] = {
createEnumConfig("enable-module-command", NULL, IMMUTABLE_CONFIG, protected_action_enum, server.enable_module_cmd, PROTECTED_ACTION_ALLOWED_NO, NULL, NULL),
createEnumConfig("cluster-preferred-endpoint-type", NULL, MODIFIABLE_CONFIG, cluster_preferred_endpoint_type_enum, server.cluster_preferred_endpoint_type, CLUSTER_ENDPOINT_TYPE_IP, NULL, NULL),
createEnumConfig("propagation-error-behavior", NULL, MODIFIABLE_CONFIG, propagation_error_behavior_enum, server.propagation_error_behavior, PROPAGATION_ERR_BEHAVIOR_IGNORE, NULL, NULL),
+ createEnumConfig("shutdown-on-sigint", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, shutdown_on_sig_enum, server.shutdown_on_sigint, 0, isValidShutdownOnSigFlags, NULL),
+ createEnumConfig("shutdown-on-sigterm", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, shutdown_on_sig_enum, server.shutdown_on_sigterm, 0, isValidShutdownOnSigFlags, NULL),
/* Integer configs */
createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, server.dbnum, 16, INTEGER_CONFIG, NULL, NULL),
diff --git a/src/server.c b/src/server.c
index dbfe5aaea..629368cf4 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1213,10 +1213,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
cronUpdateMemoryStats();
- /* We received a SIGTERM, shutting down here in a safe way, as it is
+ /* We received a SIGTERM or SIGINT, shutting down here in a safe way, as it is
* not ok doing so inside the signal handler. */
if (server.shutdown_asap && !isShutdownInitiated()) {
- if (prepareForShutdown(SHUTDOWN_NOFLAGS) == C_OK) exit(0);
+ int shutdownFlags = SHUTDOWN_NOFLAGS;
+ if (server.last_sig_received == SIGINT && server.shutdown_on_sigint)
+ shutdownFlags = server.shutdown_on_sigint;
+ else if (server.last_sig_received == SIGTERM && server.shutdown_on_sigterm)
+ shutdownFlags = server.shutdown_on_sigterm;
+
+ if (prepareForShutdown(shutdownFlags) == C_OK) exit(0);
} else if (isShutdownInitiated()) {
if (server.mstime >= server.shutdown_mstime || isReadyToShutdown()) {
if (finishShutdown() == C_OK) exit(0);
@@ -1469,6 +1475,7 @@ void whileBlockedCron() {
if (prepareForShutdown(SHUTDOWN_NOSAVE) == C_OK) exit(0);
serverLog(LL_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
server.shutdown_asap = 0;
+ server.last_sig_received = 0;
}
}
@@ -2542,6 +2549,7 @@ void initServer(void) {
server.aof_last_write_status = C_OK;
server.aof_last_write_errno = 0;
server.repl_good_slaves_count = 0;
+ server.last_sig_received = 0;
/* Create the timer callback, this is our way to process many background
* operations incrementally, like clients timeout, eviction of unaccessed
@@ -4012,6 +4020,7 @@ static void cancelShutdown(void) {
server.shutdown_asap = 0;
server.shutdown_flags = 0;
server.shutdown_mstime = 0;
+ server.last_sig_received = 0;
replyToClientsBlockedOnShutdown();
unpauseClients(PAUSE_DURING_SHUTDOWN);
}
@@ -6302,6 +6311,7 @@ static void sigShutdownHandler(int sig) {
serverLogFromHandler(LL_WARNING, msg);
server.shutdown_asap = 1;
+ server.last_sig_received = sig;
}
void setupSignalHandlers(void) {
diff --git a/src/server.h b/src/server.h
index 7d284cb04..36128aac9 100644
--- a/src/server.h
+++ b/src/server.h
@@ -1462,6 +1462,7 @@ struct redisServer {
redisAtomic unsigned int lruclock; /* Clock for LRU eviction */
volatile sig_atomic_t shutdown_asap; /* Shutdown ordered by signal handler. */
mstime_t shutdown_mstime; /* Timestamp to limit graceful shutdown. */
+ int last_sig_received; /* Indicates the last SIGNAL received, if any (e.g., SIGINT or SIGTERM). */
int shutdown_flags; /* Flags passed to prepareForShutdown(). */
int activerehashing; /* Incremental rehash in serverCron() */
int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */
@@ -1729,6 +1730,8 @@ struct redisServer {
* abort(). useful for Valgrind. */
/* Shutdown */
int shutdown_timeout; /* Graceful shutdown time limit in seconds. */
+ int shutdown_on_sigint; /* Shutdown flags configured for SIGINT. */
+ int shutdown_on_sigterm; /* Shutdown flags configured for SIGTERM. */
/* Replication (master) */
char replid[CONFIG_RUN_ID_SIZE+1]; /* My current replication ID. */
diff --git a/tests/modules/moduleconfigs.c b/tests/modules/moduleconfigs.c
index a9e434a7b..af30bda34 100644
--- a/tests/modules/moduleconfigs.c
+++ b/tests/modules/moduleconfigs.c
@@ -139,4 +139,4 @@ int RedisModule_OnUnload(RedisModuleCtx *ctx) {
strval = NULL;
}
return REDISMODULE_OK;
-} \ No newline at end of file
+}
diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl
index 89d8f170b..4b4b8f56b 100644
--- a/tests/unit/introspection.tcl
+++ b/tests/unit/introspection.tcl
@@ -286,16 +286,22 @@ start_server {tags {"introspection"}} {
}
} {} {external:skip}
- test {CONFIG REWRITE handles save properly} {
+ test {CONFIG REWRITE handles save and shutdown properly} {
r config set save "3600 1 300 100 60 10000"
+ r config set shutdown-on-sigterm "nosave now"
+ r config set shutdown-on-sigint "save"
r config rewrite
restart_server 0 true false
assert_equal [r config get save] {save {3600 1 300 100 60 10000}}
+ assert_equal [r config get shutdown-on-sigterm] {shutdown-on-sigterm {nosave now}}
+ assert_equal [r config get shutdown-on-sigint] {shutdown-on-sigint save}
r config set save ""
+ r config set shutdown-on-sigterm "default"
r config rewrite
restart_server 0 true false
assert_equal [r config get save] {save {}}
+ assert_equal [r config get shutdown-on-sigterm] {shutdown-on-sigterm default}
start_server {config "minimal.conf"} {
assert_equal [r config get save] {save {3600 1 300 100 60 10000}}
diff --git a/tests/unit/moduleapi/moduleconfigs.tcl b/tests/unit/moduleapi/moduleconfigs.tcl
index 01aa1e88e..29682d2e2 100644
--- a/tests/unit/moduleapi/moduleconfigs.tcl
+++ b/tests/unit/moduleapi/moduleconfigs.tcl
@@ -54,7 +54,7 @@ start_server {tags {"modules"}} {
test {Enums only able to be set to passed in values} {
# Module authors specify what values are valid for enums, check that only those values are ok on a set
catch {[r config set moduleconfigs.enum four]} e
- assert_match {*argument must be one of the following*} $e
+ assert_match {*must be one of the following*} $e
}
test {Unload removes module configs} {