Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit tracking custom errors (e.g. from LUA) while allowing non custom errors to be tracked normally #500

Merged
merged 17 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/networking.c
Original file line number Diff line number Diff line change
Expand Up @@ -543,10 +543,14 @@ void afterErrorReply(client *c, const char *s, size_t len, int flags) {
if (!(flags & ERR_REPLY_FLAG_NO_STATS_UPDATE)) {
/* Increment the global error counter */
server.stat_total_error_replies++;
/* Increment the error stats
* If the string already starts with "-..." then the error prefix
* is provided by the caller ( we limit the search to 32 chars). Otherwise we use "-ERR". */
if (s[0] != '-') {
/* Increment the error stats */
/* After the errors RAX reaches this limit, instead of tracking
* custom LUA errors, we track the error under `error_LUA` */
if (flags & ERR_REPLY_FLAG_LUA && raxSize(server.errors) >= ERROR_STATS_LUA_LIMIT) {
incrementErrorCount(LUA_ERRORSTATS_OVERFLOW_ERR, strlen(LUA_ERRORSTATS_OVERFLOW_ERR));
KarthikSubbarao marked this conversation as resolved.
Show resolved Hide resolved
} else if (s[0] != '-') {
/* If the string already starts with "-..." then the error prefix
* is provided by the caller ( we limit the search to 32 chars). Otherwise we use "-ERR". */
incrementErrorCount("ERR", 3);
} else {
char *spaceloc = memchr(s, ' ', len < 32 ? len : 32);
Expand Down
5 changes: 3 additions & 2 deletions src/script_lua.c
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,9 @@ static void luaReplyToRedisReply(client *c, client *script_client, lua_State *lu
1); /* pop the error message, we will use luaExtractErrorInformation to get error information */
errorInfo err_info = {0};
luaExtractErrorInformation(lua, &err_info);
addReplyErrorFormatEx(c, err_info.ignore_err_stats_update ? ERR_REPLY_FLAG_NO_STATS_UPDATE : 0, "-%s",
err_info.msg);
addReplyErrorFormatEx(
c, ERR_REPLY_FLAG_LUA | (err_info.ignore_err_stats_update ? ERR_REPLY_FLAG_NO_STATS_UPDATE : 0), "-%s",
err_info.msg);
luaErrorInformationDiscard(&err_info);
lua_pop(lua, 1); /* pop the result table */
return;
Expand Down
41 changes: 0 additions & 41 deletions src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -2531,7 +2531,6 @@ void initServer(void) {
server.main_thread_id = pthread_self();
server.current_client = NULL;
server.errors = raxNew();
server.errors_enabled = 1;
server.execution_nesting = 0;
server.clients = listCreate();
server.clients_index = raxNew();
Expand Down Expand Up @@ -3011,7 +3010,6 @@ void resetCommandTableStats(dict *commands) {
void resetErrorTableStats(void) {
freeErrorsRadixTreeAsync(server.errors);
server.errors = raxNew();
server.errors_enabled = 1;
}

/* ========================== OP Array API ============================ */
Expand Down Expand Up @@ -4048,48 +4046,9 @@ int processCommand(client *c) {

/* ====================== Error lookup and execution ===================== */

/* Users who abuse lua error_reply will generate a new error object on each
* error call, which can make server.errors get bigger and bigger. This will
* cause the server to block when calling INFO (we also return errorstats by
* default). To prevent the damage it can cause, when a misuse is detected,
* we will print the warning log and disable the errorstats to avoid adding
* more new errors. It can be re-enabled via CONFIG RESETSTAT. */
#define ERROR_STATS_NUMBER 128
void incrementErrorCount(const char *fullerr, size_t namelen) {
/* errorstats is disabled, return ASAP. */
if (!server.errors_enabled) return;

void *result;
if (!raxFind(server.errors, (unsigned char *)fullerr, namelen, &result)) {
if (server.errors->numele >= ERROR_STATS_NUMBER) {
sds errors = sdsempty();
raxIterator ri;
raxStart(&ri, server.errors);
raxSeek(&ri, "^", NULL, 0);
while (raxNext(&ri)) {
char *tmpsafe;
errors = sdscatlen(errors, getSafeInfoString((char *)ri.key, ri.key_len, &tmpsafe), ri.key_len);
errors = sdscatlen(errors, ", ", 2);
if (tmpsafe != NULL) zfree(tmpsafe);
}
sdsrange(errors, 0, -3); /* Remove final ", ". */
raxStop(&ri);

/* Print the warning log and the contents of server.errors to the log. */
serverLog(LL_WARNING, "Errorstats stopped adding new errors because the number of "
"errors reached the limit, may be misuse of lua error_reply, "
"please check INFO ERRORSTATS, this can be re-enabled via "
"CONFIG RESETSTAT.");
serverLog(LL_WARNING, "Current errors code list: %s", errors);
sdsfree(errors);

/* Reset the errors and add a single element to indicate that it is disabled. */
resetErrorTableStats();
incrementErrorCount("ERRORSTATS_DISABLED", 19);
server.errors_enabled = 0;
return;
}

struct serverError *error = zmalloc(sizeof(*error));
error->count = 1;
raxInsert(server.errors, (unsigned char *)fullerr, namelen, error, NULL);
Expand Down
11 changes: 10 additions & 1 deletion src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -1580,7 +1580,6 @@ struct valkeyServer {
dict *orig_commands; /* Command table before command renaming. */
aeEventLoop *el;
rax *errors; /* Errors table */
int errors_enabled; /* If true, errorstats is enabled, and we will add new errors. */
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. */
Expand Down Expand Up @@ -2595,10 +2594,20 @@ int serverCommunicateSystemd(const char *sd_notify_msg);
void serverSetCpuAffinity(const char *cpulist);
void dictVanillaFree(dict *d, void *val);

/* ERROR STATS constants */
#define ERROR_STATS_LUA_LIMIT \
128 /* After the errors RAX reaches this limit, instead of tracking \
custom LUA errors, we track the error under the symbol below. */
KarthikSubbarao marked this conversation as resolved.
Show resolved Hide resolved
#define LUA_ERRORSTATS_OVERFLOW_ERR "LUA_ERRORSTATS_OVERFLOW"

/* afterErrorReply flags */
zuiderkwast marked this conversation as resolved.
Show resolved Hide resolved
#define ERR_REPLY_FLAG_NO_STATS_UPDATE \
(1ULL << 0) /* Indicating that we should not update \
error stats after sending error reply */
#define ERR_REPLY_FLAG_LUA \
(1ULL << 1) /* Indicating that the error message is from LUA replying \
to a client */

/* networking.c -- Networking and Client related operations */
client *createClient(connection *conn);
void freeClient(client *c);
Expand Down
33 changes: 20 additions & 13 deletions tests/unit/info.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -278,21 +278,28 @@ start_server {tags {"info" "external:skip"}} {
r config resetstat
for {set j 1} {$j <= 1100} {incr j} {
assert_error "$j my error message" {
r eval {return redis.error_reply(string.format('%s my error message', ARGV[1]))} 0 $j
r eval {return server.error_reply(string.format('%s my error message', ARGV[1]))} 0 $j
}
}

assert_equal [count_log_message 0 "Errorstats stopped adding new errors"] 1
assert_equal [count_log_message 0 "Current errors code list"] 1
assert_equal "count=1" [errorstat ERRORSTATS_DISABLED]

# Since we currently have no metrics exposed for server.errors, we use lazyfree
# to verify that we only have 128 errors.
wait_for_condition 50 100 {
[s lazyfreed_objects] eq 128
} else {
fail "errorstats resetstat lazyfree error"
}
# Validate that custom LUA errors are tracked in `LUA_ERRORSTATS_OVERFLOW` when errors
# has 128 entries.
assert_equal "count=972" [errorstat LUA_ERRORSTATS_OVERFLOW]
# Validate that non LUA errors continue to be tracked even when we have >=128 entries.
assert_error {ERR syntax error} {r set a b c d e f g}
assert_equal "count=1" [errorstat ERR]
# Test LUA error variants.
assert_error "My error message" {r eval {return server.error_reply('My error message')} 0}
assert_error "My error message" {r eval {return {err = 'My error message'}} 0}
assert_equal "count=974" [errorstat LUA_ERRORSTATS_OVERFLOW]
# Function calls that contain custom error messages should call be included in overflow counter
r FUNCTION LOAD replace [format "#!lua name=mylib\nserver.register_function('customerrorfn', function() return server.error_reply('My error message') end)"]
assert_error "My error message" {r fcall customerrorfn 0}
assert_equal "count=975" [errorstat LUA_ERRORSTATS_OVERFLOW]
# Function calls that contain non lua errors should continue to be tracked normally (in a separate counter).
r FUNCTION LOAD replace [format "#!lua name=mylib\nserver.register_function('invalidgetcmd', function() return server.call('get', 'x', 'x', 'x') end)"]
assert_error "ERR Wrong number of args*" {r fcall invalidgetcmd 0}
assert_equal "count=975" [errorstat LUA_ERRORSTATS_OVERFLOW]
assert_equal "count=2" [errorstat ERR]
}

test {stats: eventloop metrics} {
Expand Down
Loading