-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AsyncFd reports socket misleadingly as readable and hangs in an endless loop #4549
Comments
I've also repro'd this. I'm gonna take a look at this later tonight. |
I had time to dig a little bit into this topic with my rough knowledge of Tokio code. These are my findings so far:
It is intended not to clear read_closed and write_closed as the comment points out. Is it sparsed out to handle the shutdown case? I haven't found out yet why the socket is reported read_closed and write_closed only on multi_thread configuration. |
I think I found the problem. It is a problem on my side and I was able to reproduce it with plain epoll as well. I am creating the AsynFd in AsyncSocket::new(). There it is registered to epoll. A little bit later I set the underlying socket to listen mode. This triggers EPOLLHUP to the already registered socket which sets the event to read_closed and write_closed and starts the loop. I should do that all on the right place in new() before AsyncFd is created. |
It wasn't the full solution. I still face the problem. On server side, if the call to listen() is missing Mio(epoll) repeats immediately read_close/write_close after the call to AsyncFd::new(). This will not happen, if the call to listen() happens. On client side there is no call to listen(), so I get read_close/write_close after the call to AsyncFd::new() immediately from Mio(epoll). As these events are excluded by clear_readiness(), the socket is always reported readable/writable but it isn't. So the questions:
use std::time::Duration;
use tokio::io::unix::AsyncFd;
pub fn create_address<A: std::net::ToSocketAddrs>(
address: A,
) -> std::io::Result<std::net::SocketAddr> {
Ok(address.to_socket_addrs()?.next().unwrap())
}
#[tokio::main]
async fn main() {
//chose between client and server
//server().await;
client().await;
}
async fn server() {
let local_address_server = create_address(("127.0.0.1", 5000)).unwrap();
let server_socket = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::STREAM, None).unwrap();
server_socket.bind(&socket2::SockAddr::from(local_address_server)).unwrap();
//Comment out listen() and read_closed/write_closed it reported which enters an endless loop because these events are not cleared
server_socket.listen(128).unwrap();
server_socket.set_nonblocking(true).unwrap();
//After calling AsyncFd::new read_closed/write_closed is reported by Mio(epoll) if listen() is commented out
let mut server = AsyncFd::new(server_socket).unwrap();
loop {
let mut guard = server.readable_mut().await.unwrap();
println!("Server readable. Should not be seen or at least not in a loop");
tokio::time::sleep(Duration::from_millis(1000)).await;
guard.clear_ready();
}
}
async fn client() {
let local_address_client = create_address(("127.0.0.1", 5001)).unwrap();
let client_socket = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::STREAM, None).unwrap();
client_socket.bind(&socket2::SockAddr::from(local_address_client)).unwrap();
client_socket.set_nonblocking(true).unwrap();
//After calling AsyncFd::new read_closed/write_closed is reported by Mio(epoll)
let mut client = AsyncFd::new(client_socket).unwrap();
loop {
let mut guard = client.readable_mut().await.unwrap();
println!("Client readable. Should not be seen or at least not in a loop");
tokio::time::sleep(Duration::from_millis(1000)).await;
guard.clear_ready();
}
} |
Are you not missing a call to |
You are right. I have to call first connect and then register it to epoll. Otherwise I get EPOLLHUP which is read_close/write_close. I will try that tomorrow. I missed that. I use socket2 as a replacement here as an example. On my project I need to use a third party C library that I want to use with Tokio and a FFI I created. This has formal/regulatory reasons that I cannot replace the C-Lib with beautiful Rust stuff. That would be to easy. |
In any case, you should be sure to study how |
Finally I was able to get my stuff running and it works as expected. Thanks for help. |
Version
Platform
Linux HOSTNAME 5.10.0-11-amd64 #1 SMP Debian 5.10.92-1 (2022-01-18) x86_64 GNU/Linux
Description
Socket is reported misleadingly as readable. But it isn't. The command returns WOULDBLOCK but the socket is reported still as readable. So I encounter an endless loop which is never leaved. This issue started from a discussion: #4529
I want to use a plain Linux socket/file descriptor with Tokio and the help of AsyncFd. I created an example where socket2::Socket is a replacement for the stuff I want to integrate and I was able to reproduce the behavior. If I start the test communication_async_multi_thread the underlying accept is called in an endless loop. This happens not on the first or second time. This test needs to be executed multiple times. But after some tries accept enters an endless loop because the socket is reported as readable. This should not happen. The test communication_async_current_thread works as expected. I was able to execute it a few dozen times without problems. But maybe on this configuration this problems happens not so often and the problem is not revealed.
I created also a binary for testing purposes with the same problem.
The text was updated successfully, but these errors were encountered: