mirror of
https://github.com/torvalds/linux.git
synced 2026-07-29 18:51:21 +02:00
Merge branch 'vsock-sock_linger-rework'
Michal Luczaj says: ==================== vsock: SOCK_LINGER rework Change vsock's lingerning to wait on close() until all data is sent, i.e. until workers picked all the packets for processing. v5: https://lore.kernel.org/r/20250521-vsock-linger-v5-0-94827860d1d6@rbox.co v4: https://lore.kernel.org/r/20250501-vsock-linger-v4-0-beabbd8a0847@rbox.co v3: https://lore.kernel.org/r/20250430-vsock-linger-v3-0-ddbe73b53457@rbox.co v2: https://lore.kernel.org/r/20250421-vsock-linger-v2-0-fe9febd64668@rbox.co v1: https://lore.kernel.org/r/20250407-vsock-linger-v1-0-1458038e3492@rbox.co Signed-off-by: Michal Luczaj <mhal@rbox.co> ==================== Link: https://patch.msgid.link/20250522-vsock-linger-v6-0-2ad00b0e447e@rbox.co Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
commit
35a7a2f555
|
|
@ -221,6 +221,7 @@ void vsock_for_each_connected_socket(struct vsock_transport *transport,
|
|||
void (*fn)(struct sock *sk));
|
||||
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
|
||||
bool vsock_find_cid(unsigned int cid);
|
||||
void vsock_linger(struct sock *sk);
|
||||
|
||||
/**** TAP ****/
|
||||
|
||||
|
|
|
|||
|
|
@ -1013,6 +1013,39 @@ static int vsock_getname(struct socket *sock,
|
|||
return err;
|
||||
}
|
||||
|
||||
void vsock_linger(struct sock *sk)
|
||||
{
|
||||
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
||||
ssize_t (*unsent)(struct vsock_sock *vsk);
|
||||
struct vsock_sock *vsk = vsock_sk(sk);
|
||||
long timeout;
|
||||
|
||||
if (!sock_flag(sk, SOCK_LINGER))
|
||||
return;
|
||||
|
||||
timeout = sk->sk_lingertime;
|
||||
if (!timeout)
|
||||
return;
|
||||
|
||||
/* Transports must implement `unsent_bytes` if they want to support
|
||||
* SOCK_LINGER through `vsock_linger()` since we use it to check when
|
||||
* the socket can be closed.
|
||||
*/
|
||||
unsent = vsk->transport->unsent_bytes;
|
||||
if (!unsent)
|
||||
return;
|
||||
|
||||
add_wait_queue(sk_sleep(sk), &wait);
|
||||
|
||||
do {
|
||||
if (sk_wait_event(sk, &timeout, unsent(vsk) == 0, &wait))
|
||||
break;
|
||||
} while (!signal_pending(current) && timeout);
|
||||
|
||||
remove_wait_queue(sk_sleep(sk), &wait);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(vsock_linger);
|
||||
|
||||
static int vsock_shutdown(struct socket *sock, int mode)
|
||||
{
|
||||
int err;
|
||||
|
|
|
|||
|
|
@ -1191,23 +1191,6 @@ static void virtio_transport_remove_sock(struct vsock_sock *vsk)
|
|||
vsock_remove_sock(vsk);
|
||||
}
|
||||
|
||||
static void virtio_transport_wait_close(struct sock *sk, long timeout)
|
||||
{
|
||||
if (timeout) {
|
||||
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
||||
|
||||
add_wait_queue(sk_sleep(sk), &wait);
|
||||
|
||||
do {
|
||||
if (sk_wait_event(sk, &timeout,
|
||||
sock_flag(sk, SOCK_DONE), &wait))
|
||||
break;
|
||||
} while (!signal_pending(current) && timeout);
|
||||
|
||||
remove_wait_queue(sk_sleep(sk), &wait);
|
||||
}
|
||||
}
|
||||
|
||||
static void virtio_transport_cancel_close_work(struct vsock_sock *vsk,
|
||||
bool cancel_timeout)
|
||||
{
|
||||
|
|
@ -1277,8 +1260,8 @@ static bool virtio_transport_close(struct vsock_sock *vsk)
|
|||
if ((sk->sk_shutdown & SHUTDOWN_MASK) != SHUTDOWN_MASK)
|
||||
(void)virtio_transport_shutdown(vsk, SHUTDOWN_MASK);
|
||||
|
||||
if (sock_flag(sk, SOCK_LINGER) && !(current->flags & PF_EXITING))
|
||||
virtio_transport_wait_close(sk, sk->sk_lingertime);
|
||||
if (!(current->flags & PF_EXITING))
|
||||
vsock_linger(sk);
|
||||
|
||||
if (sock_flag(sk, SOCK_DONE)) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <assert.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/sockios.h>
|
||||
|
||||
#include "timeout.h"
|
||||
#include "control.h"
|
||||
|
|
@ -96,6 +97,30 @@ void vsock_wait_remote_close(int fd)
|
|||
close(epollfd);
|
||||
}
|
||||
|
||||
/* Wait until transport reports no data left to be sent.
|
||||
* Return false if transport does not implement the unsent_bytes() callback.
|
||||
*/
|
||||
bool vsock_wait_sent(int fd)
|
||||
{
|
||||
int ret, sock_bytes_unsent;
|
||||
|
||||
timeout_begin(TIMEOUT);
|
||||
do {
|
||||
ret = ioctl(fd, SIOCOUTQ, &sock_bytes_unsent);
|
||||
if (ret < 0) {
|
||||
if (errno == EOPNOTSUPP)
|
||||
break;
|
||||
|
||||
perror("ioctl(SIOCOUTQ)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
timeout_check("SIOCOUTQ");
|
||||
} while (sock_bytes_unsent != 0);
|
||||
timeout_end();
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
/* Create socket <type>, bind to <cid, port> and return the file descriptor. */
|
||||
int vsock_bind(unsigned int cid, unsigned int port, int type)
|
||||
{
|
||||
|
|
@ -798,3 +823,16 @@ void enable_so_zerocopy_check(int fd)
|
|||
setsockopt_int_check(fd, SOL_SOCKET, SO_ZEROCOPY, 1,
|
||||
"setsockopt SO_ZEROCOPY");
|
||||
}
|
||||
|
||||
void enable_so_linger(int fd, int timeout)
|
||||
{
|
||||
struct linger optval = {
|
||||
.l_onoff = 1,
|
||||
.l_linger = timeout
|
||||
};
|
||||
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval))) {
|
||||
perror("setsockopt(SO_LINGER)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ int vsock_stream_listen(unsigned int cid, unsigned int port);
|
|||
int vsock_seqpacket_accept(unsigned int cid, unsigned int port,
|
||||
struct sockaddr_vm *clientaddrp);
|
||||
void vsock_wait_remote_close(int fd);
|
||||
bool vsock_wait_sent(int fd);
|
||||
void send_buf(int fd, const void *buf, size_t len, int flags,
|
||||
ssize_t expected_ret);
|
||||
void recv_buf(int fd, void *buf, size_t len, int flags, ssize_t expected_ret);
|
||||
|
|
@ -79,4 +80,5 @@ void setsockopt_int_check(int fd, int level, int optname, int val,
|
|||
void setsockopt_timeval_check(int fd, int level, int optname,
|
||||
struct timeval val, char const *errmsg);
|
||||
void enable_so_zerocopy_check(int fd);
|
||||
void enable_so_linger(int fd, int timeout);
|
||||
#endif /* UTIL_H */
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/sockios.h>
|
||||
#include <linux/time64.h>
|
||||
|
||||
#include "vsock_test_zerocopy.h"
|
||||
|
|
@ -1280,7 +1279,7 @@ static void test_unsent_bytes_server(const struct test_opts *opts, int type)
|
|||
static void test_unsent_bytes_client(const struct test_opts *opts, int type)
|
||||
{
|
||||
unsigned char buf[MSG_BUF_IOCTL_LEN];
|
||||
int ret, fd, sock_bytes_unsent;
|
||||
int fd;
|
||||
|
||||
fd = vsock_connect(opts->peer_cid, opts->peer_port, type);
|
||||
if (fd < 0) {
|
||||
|
|
@ -1297,22 +1296,12 @@ static void test_unsent_bytes_client(const struct test_opts *opts, int type)
|
|||
/* SIOCOUTQ isn't guaranteed to instantly track sent data. Even though
|
||||
* the "RECEIVED" message means that the other side has received the
|
||||
* data, there can be a delay in our kernel before updating the "unsent
|
||||
* bytes" counter. Repeat SIOCOUTQ until it returns 0.
|
||||
* bytes" counter. vsock_wait_sent() will repeat SIOCOUTQ until it
|
||||
* returns 0.
|
||||
*/
|
||||
timeout_begin(TIMEOUT);
|
||||
do {
|
||||
ret = ioctl(fd, SIOCOUTQ, &sock_bytes_unsent);
|
||||
if (ret < 0) {
|
||||
if (errno == EOPNOTSUPP) {
|
||||
fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
|
||||
break;
|
||||
}
|
||||
perror("ioctl");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
timeout_check("SIOCOUTQ");
|
||||
} while (sock_bytes_unsent != 0);
|
||||
timeout_end();
|
||||
if (!vsock_wait_sent(fd))
|
||||
fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
|
|
@ -1824,10 +1813,6 @@ static void test_stream_connect_retry_server(const struct test_opts *opts)
|
|||
|
||||
static void test_stream_linger_client(const struct test_opts *opts)
|
||||
{
|
||||
struct linger optval = {
|
||||
.l_onoff = 1,
|
||||
.l_linger = 1
|
||||
};
|
||||
int fd;
|
||||
|
||||
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
|
||||
|
|
@ -1836,11 +1821,7 @@ static void test_stream_linger_client(const struct test_opts *opts)
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval))) {
|
||||
perror("setsockopt(SO_LINGER)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
enable_so_linger(fd, 1);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
|
|
@ -1858,6 +1839,53 @@ static void test_stream_linger_server(const struct test_opts *opts)
|
|||
close(fd);
|
||||
}
|
||||
|
||||
/* Half of the default to not risk timing out the control channel */
|
||||
#define LINGER_TIMEOUT (TIMEOUT / 2)
|
||||
|
||||
static void test_stream_nolinger_client(const struct test_opts *opts)
|
||||
{
|
||||
bool waited;
|
||||
time_t ns;
|
||||
int fd;
|
||||
|
||||
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
|
||||
if (fd < 0) {
|
||||
perror("connect");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
enable_so_linger(fd, LINGER_TIMEOUT);
|
||||
send_byte(fd, 1, 0); /* Left unread to expose incorrect behaviour. */
|
||||
waited = vsock_wait_sent(fd);
|
||||
|
||||
ns = current_nsec();
|
||||
close(fd);
|
||||
ns = current_nsec() - ns;
|
||||
|
||||
if (!waited) {
|
||||
fprintf(stderr, "Test skipped, SIOCOUTQ not supported.\n");
|
||||
} else if (DIV_ROUND_UP(ns, NSEC_PER_SEC) >= LINGER_TIMEOUT) {
|
||||
fprintf(stderr, "Unexpected lingering\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
control_writeln("DONE");
|
||||
}
|
||||
|
||||
static void test_stream_nolinger_server(const struct test_opts *opts)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
|
||||
if (fd < 0) {
|
||||
perror("accept");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
control_expectln("DONE");
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static struct test_case test_cases[] = {
|
||||
{
|
||||
.name = "SOCK_STREAM connection reset",
|
||||
|
|
@ -2018,6 +2046,11 @@ static struct test_case test_cases[] = {
|
|||
.run_client = test_stream_linger_client,
|
||||
.run_server = test_stream_linger_server,
|
||||
},
|
||||
{
|
||||
.name = "SOCK_STREAM SO_LINGER close() on unread",
|
||||
.run_client = test_stream_nolinger_client,
|
||||
.run_server = test_stream_nolinger_server,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user