Hi,
I originally reported this for tokio, but the same happens even with std::net::TcpStream.
The issue is that, given a TcpStream connected to a remote TCP server, when the server goes down, it is still possible to call the write
methods on the stream without being notified of the failure.
This is the tested flow:
- Start a TCP server
- a TcpStream connects to the server
- the TcpStream sends 'message_1' -> the message is received by the server
- the TcpStream sends 'shutdown' -> the server receives the message and stops itself
- the TcpStream sends 'message_2' -> HERE THE ISSUE , in fact, both the
write
and theflush
methods complete successfully, but the message is lost because the server is not available - the TcpStream sends 'message_3' -> at this point the
write
fails, but it should have failed already on 'message_2'
I am very confused about this behaviour. How can both the TcpStream.write
and the TcpStream.flush
calls return Ok()
?
Am I doing something wrong?
use std::net::{TcpListener, TcpStream};
use std::io::BufReader;
use std::io::BufRead;
use std::io::Write;
use std::time;
use std::thread;
const BASE_ADDRESS: &str = "127.0.0.1";
#[test]
fn std_should_not_lose_tcp_requests() {
let port = 8080;
let address = format!("{}:{}", BASE_ADDRESS, port);
let address_clone = address.clone();
// start a TCP server
thread::spawn(move || start_server(address_clone));
thread::sleep(time::Duration::new(1, 0));
// Create a TcpStream connected to the server
let mut stream = TcpStream::connect(&address).unwrap();
// send `message_1' -> Ok
assert!(send_bytes(&mut stream, "message_1\n").is_ok());
thread::sleep(time::Duration::new(1, 0));
// send 'shutdown' -> Ok, the server is stopped
assert!(send_bytes(&mut stream, "shutdown\n").is_ok());
// sleep alot to be really really really sure the server has time to go down
thread::sleep(time::Duration::new(10, 0));
// A new connection correctly fails as the server is not available
assert!(TcpStream::connect(&address).is_err());
// HERE THE ISSUE
// THIS ASSERT FAILS: both the write and the flush complete successfully even if the server
// is not available. Consequently, the message is silently lost.
assert!(send_bytes(&mut stream, "message_2\n").is_err());
// Sending a second message after the server went down correctly fails.
// Anyway, we expected the failure to happen on the previous message.
assert!(send_bytes(&mut stream, "message_3\n").is_err());
}
fn send_bytes<R: Write>(send: &mut R, message: &str) -> Result<(), std::io::Error> {
send.write_all(message.as_bytes())?;
send.flush()?;
println!("message sent: {:?}", message);
Ok(())
}
fn start_server(address: String) {
let mut listener = TcpListener::bind(address).unwrap();
println!("TCP listener ready on port: {}", listener.local_addr().unwrap().port());
let (socket, _) = listener.accept().unwrap();
let mut reader = BufReader::new(socket);
loop {
let mut response = String::new();
reader.read_line(&mut response).unwrap();
println!("received message: {}", response);
if response.trim().eq("shutdown") {
break;
}
}
println!("Shutdown TCP server");
}