From 036963a7daa03778ed5a8a0672bb7aa5d7aa74e4 Mon Sep 17 00:00:00 2001 From: "Meir Shpilraien (Spielrein)" Date: Mon, 29 Mar 2021 13:34:16 +0300 Subject: [PATCH] Restore old client 'processCommandAndResetClient' to fix false dead client indicator (#8715) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'processCommandAndResetClient' returns 1 if client is dead. It does it by checking if serve.current_client is NULL. On script timeout, Redis will re-enter 'processCommandAndResetClient' and when finish we will set server.current_client to NULL. This will cause later to falsely return 1 and think that the client that sent the timed-out script is dead (Redis to stop reading from the client buffer). --- src/networking.c | 10 +++++++++- tests/unit/scripting.tcl | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/networking.c b/src/networking.c index bd0bcadb2..d554d748b 100644 --- a/src/networking.c +++ b/src/networking.c @@ -1990,12 +1990,20 @@ void commandProcessed(client *c) { * of processing the command, otherwise C_OK is returned. */ int processCommandAndResetClient(client *c) { int deadclient = 0; + client *old_client = server.current_client; server.current_client = c; if (processCommand(c) == C_OK) { commandProcessed(c); } if (server.current_client == NULL) deadclient = 1; - server.current_client = NULL; + /* + * Restore the old client, this is needed because when a script + * times out, we will get into this code from processEventsWhileBlocked. + * Which will cause to set the server.current_client. If not restored + * we will return 1 to our caller which will falsely indicate the client + * is dead and will stop reading from its buffer. + */ + server.current_client = old_client; /* performEvictions may flush slave output buffers. This may * result in a slave, that may be the active client, to be * freed. */ diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index 92ca05bdc..0efe34cad 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -640,6 +640,43 @@ start_server {tags {"scripting"}} { assert_match {*killed by user*} $res } + test {Timedout script does not cause a false dead client} { + set rd [redis_deferring_client] + r config set lua-time-limit 10 + + # senging (in a pipeline): + # 1. eval "while 1 do redis.call('ping') end" 0 + # 2. ping + set buf "*3\r\n\$4\r\neval\r\n\$33\r\nwhile 1 do redis.call('ping') end\r\n\$1\r\n0\r\n" + append buf "*1\r\n\$4\r\nping\r\n" + $rd write $buf + $rd flush + + wait_for_condition 50 100 { + [catch {r ping} e] == 1 + } else { + fail "Can't wait for script to start running" + } + catch {r ping} e + assert_match {BUSY*} $e + + r script kill + wait_for_condition 50 100 { + [catch {r ping} e] == 0 + } else { + fail "Can't wait for script to be killed" + } + assert_equal [r ping] "PONG" + + catch {$rd read} res + assert_match {*killed by user*} $res + + set res [$rd read] + assert_match {*PONG*} $res + + $rd close + } + test {Timedout script link is still usable after Lua returns} { r config set lua-time-limit 10 r eval {for i=1,100000 do redis.call('ping') end return 'ok'} 0