diff options
author | Binbin <binloveplay1314@qq.com> | 2022-11-05 00:46:37 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-04 18:46:37 +0200 |
commit | fac188b49d9680fdeb90332f163e5634cec1ea13 (patch) | |
tree | f39837f530ebc0ed32e7edc2cae2020807900431 | |
parent | c337c0a8a49d7cb64617b0a414d05b31425666f7 (diff) | |
download | redis-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.h | 7 | ||||
-rw-r--r-- | src/networking.c | 2 | ||||
-rw-r--r-- | src/socket.c | 9 | ||||
-rw-r--r-- | src/tls.c | 16 | ||||
-rw-r--r-- | src/unix.c | 7 | ||||
-rw-r--r-- | tests/unit/introspection.tcl | 38 |
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 */ @@ -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} { |