Sorry, it took a while (and I could reproduce this with muvm, too): On Sun, 29 Dec 2024 13:25:30 +0100 Stefano Brivio <sbrivio(a)redhat.com> wrote:On Sun, 29 Dec 2024 00:31:11 +0900 Asahi Lina <lina(a)asahilina.net> wrote:So, it looks like there are no paths where we would get EAGAIN with a zero peeking offset. Setting STALLED in both tcp_buf_data_from_sock() and tcp_vu_data_from_sock() before returning on EAGAIN actually implements what you suggested, solves the problem, and doesn't create new ones (at least in my tests). About setting EPOLLET unconditionally, we also discussed this with David Gibson in the latest development call: we could, in theory (and he even tried in the past), because there are two cases where we return before dequeuing all the data available from the socket (and we need a subsequent wake-up even if no further data arrives): - if we can't queue more data to the guest, in window. But then we can always rely on the guest waking us up with an ACK (or the ACK timeout handler, which we have) - if we fail to write to the hypervisor (or tap device, for containers). It shouldn't really happen under normal circumstances, but there are no guarantees. In that case, we *should* set EPOLLOUT on that descriptor, and keep track of all the sockets that had pending data, and poll them once the guest descriptor is ready for writing, otherwise they would get stuck (at least until a timeout occurs, but that might be bad enough to break functionality). But we don't have this implementation, yet. For the moment, I think we should set STALLED like you suggested (in those two functions, before the early return). Do you want to send a second version of the patch, or should I? I would actually be happy to add more names to the git log. :)I was thinking of this, maybe the easiest solution would be to just set STALLED if we get an EAGAIN *and* the current peek offset is nonzero.Actually, I think this is the correct solution (with "peek offset" here meaning the actual offset at which we peek, not the value we pass to SO_PEEK_OFF, see below), because it substantially restores the behaviour that was intended before e63d281871ef ("tcp: leverage support of SO_PEEK_OFF socket option when available"). That's what STALLED was for: if there's data on the socket, but we can't read anything more, set EPOLLET. An ACK from the guest will make us clear EPOLLET and have another look. Right now, STALLED is not always set when it should. If it's cleared because of an ACK which doesn't acknowledge all the data pending on the socket, then it's broken, because of the early return on EAGAIN. Note that we generally set SO_PEEK_OFF to 0 (and reset it on retransmissions, that is, whenever we need to "re-read" data), but that does *not* mean that the offset is zero: it means that the offset is "what we last peeked" (see also socket(7)). I know, it sounds terribly confusing, but it makes SO_PEEK_OFF actually useful (the overhead from system calls would otherwise make it not worth using). The actual peeking offset will be zero just after tcp_sock_consume(), without an intervening EPOLLIN, and in a particular case of tcp_revert_seq() (retransmission). I'm not sure if we can ever have EAGAIN from the socket with a zero peeking offset. If we can, then we need to track that. If we can't, then what you suggest is equivalent to just setting STALLED if we get EAGAIN.By the way, I wonder, if it's not too much effort to check: do you hit this without SO_PEEK_OFF (return false in tcp_probe_peek_offset_cap())?I could reproduce only with muvm and aria2c -x4 as you suggested, with a long transfer, *not* local but almost: same data centre and about ~300µs RTT. More than that, and I don't see the issue, because the guest's window is always large enough otherwise (I guess). But yes, there's a peculiarity with muvm: the MTU is set to just 1500 bytes, whereas we typically have 65520 bytes with QEMU. With user-mode networking, given that packetisation is (re-)done on the host, anything less than what the guest interface supports doesn't make sense. I'm using dhcpcd, but https://github.com/AsahiLinux/muvm/pull/111 makes no difference (yet) because I forgot to handle the MTU there (passt advertises 65520). For some reason, dhcpcd is also not setting the MTU. I'll fix this there (or in a follow-up change). If I manually set 65520 bytes, by the way, I can't reproduce the issue anymore. -- StefanoPerhaps, yes, thanks for the tip. That's something I do quite frequently with QEMU guests (updating distribution packages from a mirror very close, large parallel transfers with iperf3, etc.), but maybe the virtio-net implementation in libkrun is somehow peculiar in this regard.Give me a few days... unless you or somebody else can look into it, of course.Yeah, I think it's large parallel transfers from a fast CDN (easily maxes out a gigabit internet connection). Perhaps an "optimized" downloader like aria2c would work similarly? `aria2c -x4 <URL>` or something like that.This brings CPU usage down from around ~80% when downloading over TCP, to ~5% (use case: passt as network transport for muvm, downloading Steam games).Hah, maybe that's the key to reproducing this reliably. My usage of passt with muvm at the moment is pretty much limited to SSH, DNS and short "test" transfers. I'll give that a try (large HTTP transfers?).