summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBinbin <binloveplay1314@qq.com>2022-11-05 00:46:37 +0800
committerGitHub <noreply@github.com>2022-11-04 18:46:37 +0200
commitfac188b49d9680fdeb90332f163e5634cec1ea13 (patch)
treef39837f530ebc0ed32e7edc2cae2020807900431
parentc337c0a8a49d7cb64617b0a414d05b31425666f7 (diff)
downloadredis-fac188b49d9680fdeb90332f163e5634cec1ea13.tar.gz
Introduce socket shutdown into connection type, used if a fork is active (#11376)
Introduce socket `shutdown()` into connection type, and use it on normal socket if a fork is active. This allows us to close client connections when there are child processes sharing the file descriptors. Fixes #10077. The reason is that since the `fork()` child is holding the file descriptors, the `close` in `unlinkClient -> connClose` isn't sufficient. The client will not realize that the connection is disconnected until the child process ends. Let's try to be conservative and only use shutdown when the fork is active.
-rw-r--r--src/connection.h7
-rw-r--r--src/networking.c2
-rw-r--r--src/socket.c9
-rw-r--r--src/tls.c16
-rw-r--r--src/unix.c7
-rw-r--r--tests/unit/introspection.tcl38
6 files changed, 75 insertions, 4 deletions
diff --git a/src/connection.h b/src/connection.h
index 61ca205fe..62dc8d157 100644
--- a/src/connection.h
+++ b/src/connection.h
@@ -80,9 +80,10 @@ typedef struct ConnectionType {
int (*addr)(connection *conn, char *ip, size_t ip_len, int *port, int remote);
int (*listen)(connListener *listener);
- /* create/close connection */
+ /* create/shutdown/close connection */
connection* (*conn_create)(void);
connection* (*conn_create_accepted)(int fd, void *priv);
+ void (*shutdown)(struct connection *conn);
void (*close)(struct connection *conn);
/* connect & accept */
@@ -240,6 +241,10 @@ static inline int connSetWriteHandlerWithBarrier(connection *conn, ConnectionCal
return conn->type->set_write_handler(conn, func, barrier);
}
+static inline void connShutdown(connection *conn) {
+ conn->type->shutdown(conn);
+}
+
static inline void connClose(connection *conn) {
conn->type->close(conn);
}
diff --git a/src/networking.c b/src/networking.c
index fdd7fc3b7..757102b19 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -1448,6 +1448,8 @@ void unlinkClient(client *c) {
}
}
}
+ /* Only use shutdown when the fork is active and we are the parent. */
+ if (server.child_type) connShutdown(c->conn);
connClose(c->conn);
c->conn = NULL;
}
diff --git a/src/socket.c b/src/socket.c
index 83000eaf8..7190d5358 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -125,6 +125,12 @@ static int connSocketConnect(connection *conn, const char *addr, int port, const
* move here as we implement additional connection types.
*/
+static void connSocketShutdown(connection *conn) {
+ if (conn->fd == -1) return;
+
+ shutdown(conn->fd, SHUT_RDWR);
+}
+
/* Close the connection and free resources. */
static void connSocketClose(connection *conn) {
if (conn->fd != -1) {
@@ -388,9 +394,10 @@ static ConnectionType CT_Socket = {
.addr = connSocketAddr,
.listen = connSocketListen,
- /* create/close connection */
+ /* create/shutdown/close connection */
.conn_create = connCreateSocket,
.conn_create_accepted = connCreateAcceptedSocket,
+ .shutdown = connSocketShutdown,
.close = connSocketClose,
/* connect & accept */
diff --git a/src/tls.c b/src/tls.c
index 6ccd279f9..d16246511 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -748,6 +748,19 @@ static int connTLSListen(connListener *listener) {
return listenToPort(listener);
}
+static void connTLSShutdown(connection *conn_) {
+ tls_connection *conn = (tls_connection *) conn_;
+
+ if (conn->ssl) {
+ if (conn->c.state == CONN_STATE_CONNECTED)
+ SSL_shutdown(conn->ssl);
+ SSL_free(conn->ssl);
+ conn->ssl = NULL;
+ }
+
+ connectionTypeTcp()->shutdown(conn_);
+}
+
static void connTLSClose(connection *conn_) {
tls_connection *conn = (tls_connection *) conn_;
@@ -1103,9 +1116,10 @@ static ConnectionType CT_TLS = {
.addr = connTLSAddr,
.listen = connTLSListen,
- /* create/close connection */
+ /* create/shutdown/close connection */
.conn_create = connCreateTLS,
.conn_create_accepted = connCreateAcceptedTLS,
+ .shutdown = connTLSShutdown,
.close = connTLSClose,
/* connect & accept */
diff --git a/src/unix.c b/src/unix.c
index c4ffad4fb..6898a3c54 100644
--- a/src/unix.c
+++ b/src/unix.c
@@ -103,6 +103,10 @@ static void connUnixAcceptHandler(aeEventLoop *el, int fd, void *privdata, int m
}
}
+static void connUnixShutdown(connection *conn) {
+ connectionTypeTcp()->shutdown(conn);
+}
+
static void connUnixClose(connection *conn) {
connectionTypeTcp()->close(conn);
}
@@ -162,9 +166,10 @@ static ConnectionType CT_Unix = {
.addr = connUnixAddr,
.listen = connUnixListen,
- /* create/close connection */
+ /* create/shutdown/close connection */
.conn_create = connCreateUnix,
.conn_create_accepted = connCreateAcceptedUnix,
+ .shutdown = connUnixShutdown,
.close = connUnixClose,
/* connect & accept */
diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl
index 73f1649a7..25e46d6ab 100644
--- a/tests/unit/introspection.tcl
+++ b/tests/unit/introspection.tcl
@@ -54,6 +54,44 @@ start_server {tags {"introspection"}} {
# After killing `me`, the first ping will throw an error
assert_error "*I/O error*" {r ping}
assert_equal "PONG" [r ping]
+
+ $rd1 close
+ $rd2 close
+ $rd3 close
+ $rd4 close
+ }
+
+ test "CLIENT KILL close the client connection during bgsave" {
+ # Start a slow bgsave, trigger an active fork.
+ r flushall
+ r set k v
+ r config set rdb-key-save-delay 10000000
+ r bgsave
+ wait_for_condition 1000 10 {
+ [s rdb_bgsave_in_progress] eq 1
+ } else {
+ fail "bgsave did not start in time"
+ }
+
+ # Kill (close) the connection
+ r client kill skipme no
+
+ # In the past, client connections needed to wait for bgsave
+ # to end before actually closing, now they are closed immediately.
+ assert_error "*I/O error*" {r ping} ;# get the error very quickly
+ assert_equal "PONG" [r ping]
+
+ # Make sure the bgsave is still in progress
+ assert_equal [s rdb_bgsave_in_progress] 1
+
+ # Stop the child before we proceed to the next test
+ r config set rdb-key-save-delay 0
+ r flushall
+ wait_for_condition 1000 10 {
+ [s rdb_bgsave_in_progress] eq 0
+ } else {
+ fail "bgsave did not stop in time"
+ }
}
test {MONITOR can log executed commands} {