mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] io_uring/net: don't retry a non-blocking connect() that fails with -EAGAIN
@ 2026-10-27 15:09 Daan De Meyer via B4 Relay
  2026-10-27 16:44 ` Jens Axboe
  0 siblings, 1 reply; 2+ messages in thread
From: Daan De Meyer via B4 Relay @ 2026-10-27 15:09 UTC (permalink / raw)
  To: Jens Axboe; +Cc: io-uring, linux-kernel, Daan De Meyer

From: Daan De Meyer <daan@amutable.com>

IORING_OP_CONNECT on an O_NONBLOCK AF_UNIX stream socket retries a
connect that fails with -EAGAIN because the listener's backlog is full.
Polling the client socket doesn't help: an unconnected stream socket
reports EPOLLOUT and EPOLLHUP right away, before the backlog drains. So
io_uring retries the connect until the apoll retry limit is hit, then
punts the request to io-wq. With a full backlog, io_connect() runs 128
times in the submitting task and once more in a new io-wq worker. That
worker stays in the task's thread group, so a later
unshare(CLONE_NEWUSER) fails with -EINVAL.

None of this changes the result. __sys_connect_file() adds the socket's
own O_NONBLOCK to every attempt, so the attempt from io-wq fails with
-EAGAIN as well. That -EAGAIN becomes the CQE result, and connect(2)
returns the same -EAGAIN.

Let's complete the request with -EAGAIN right away when the socket has
O_NONBLOCK set, like connect(2) does. io_accept() already completes
-EAGAIN this way for IORING_ACCEPT_DONTWAIT. -EINPROGRESS still waits
for the connection to complete. A blocking socket still ends up in
io-wq, where the connect sleeps until the listener accepts.

Fixes: f8e85cf255ad ("io_uring: add support for IORING_OP_CONNECT")
Signed-off-by: Daan De Meyer <daan@amutable.com>
---
io_connect() calls per task with a full AF_UNIX backlog, counted with a
kprobe on io_connect():

                                  before          after
  SOCK_NONBLOCK, full backlog     128 + 1 io-wq   1, no io-wq worker
  blocking, full backlog          128 + 1 io-wq   128 + 1 io-wq
  SOCK_NONBLOCK TCP, SYN dropped  2               2

The CQE results don't change: -EAGAIN for the non-blocking AF_UNIX
socket, and 0 for the other two cases once accept() makes room.

I left the blocking case alone. The poll doesn't help there either, but
io_queue_async() always tries io_arm_poll_handler() for a pollable file,
so an issue handler can't ask for io-wq directly. Setting
REQ_F_FORCE_ASYNC in io_connect() on -EAGAIN for a blocking socket works:
the poll is ready at arm time, and io_req_task_submit() then punts the
request to io-wq. In a test build that cut the blocking case to 1 + 2
calls, and the tests below passed. Every other user sets that flag at
prep time, though, so I can send it as a follow-up if that's acceptable.

Besides AF_UNIX stream and seqpacket sockets, AF_IUCV is the only family
that returns -EAGAIN for a non-blocking connect. Its poll reports
EPOLLOUT before the connection exists, so the retries don't help there
either.

A liburing regression test, test/connect-backlog.c, will be sent
separately. It covers the non-blocking and blocking AF_UNIX cases and a
non-blocking TCP connect that gets -EINPROGRESS, on normal, SQPOLL and
DEFER_TASKRUN rings. The liburing connect, connect-rep, socket* and
accept* tests pass with and without this patch.
---
 io_uring/net.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/io_uring/net.c b/io_uring/net.c
index eeab88e77fd8..1ba3cc0f171d 100644
--- a/io_uring/net.c
+++ b/io_uring/net.c
@@ -1877,6 +1877,17 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags)
 			if (connect->seen_econnaborted)
 				goto out;
 			connect->seen_econnaborted = true;
+		} else if (req->file->f_flags & O_NONBLOCK) {
+			/*
+			 * __sys_connect_file() adds O_NONBLOCK from the socket
+			 * to every attempt, so a retry from io-wq fails with
+			 * -EAGAIN as well. A poll on the socket doesn't wait
+			 * until a retry can succeed. For example, an AF_UNIX
+			 * client reports EPOLLOUT right away while the
+			 * listener's backlog is full. Complete the request
+			 * with -EAGAIN, like connect(2) does.
+			 */
+			goto out;
 		}
 		return -EAGAIN;
 	}

---
base-commit: d135ab453a7400a34930bdda1ef4bdc8acfac80e
change-id: 20261027-b4-io-uring-connect-eagain-fbcfa542d6bb

Best regards,
--  
Daan De Meyer <daan@amutable.com>



^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-27 16:44 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 15:09 [PATCH] io_uring/net: don't retry a non-blocking connect() that fails with -EAGAIN Daan De Meyer via B4 Relay
2026-10-27 16:44 ` Jens Axboe

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®