(Originally posted here, but I got referred to "internals", so I repost it here.)
TCP connections can be terminated either successfully with the FIN flag set, or aborted with a TCP reset (RST flag), in which case previously sent data may be discarded (e.g. if the RST packet arrives early).
Under normal conditions, it is important to terminate the connection with the FIN flag. However, on critical errors, sending a TCP reset may help the peer to detect that the connection didn't terminate successfully but that something went wrong.
I noticed that dropping a TcpStream
always terminates open connections with a TCP FIN flag, even in case of panics (via unwind or abort). In case of a program abort, the FIN might even be sent out despite some buffered data not having been sent out yet.
Consider the following program:
use std::io::{Write, BufWriter};
use std::net::{Shutdown, TcpListener, TcpStream};
fn handler(stream: TcpStream) -> Result<(), Box<dyn std::error::Error>> {
let mut writer = BufWriter::new(&stream);
writeln!(writer, "Hello.")?;
Err("Some error here")?;
// or: panic!("Some error here");
// or: std::process::exit(0);
writeln!(writer, "We are done!")?;
writer.flush()?;
stream.shutdown(Shutdown::Both)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
for accepted in TcpListener::bind("[::]:1234")?.incoming() {
let stream = accepted?;
match handler(stream) {
Ok(_) => eprintln!("Handler successful"),
Err(err) => eprintln!("Error in handler: {}", err),
};
}
unreachable!();
}
Even when there is an error in the handler (either by returning prematurely or by causing a panic with stack unwinding enabled), the connection will be gracefully shut down, and the peer receives a TCP FIN.
Moreover, when I disable unwinding by setting panic = 'abort'
in my Cargo.toml
file and cause a panic (or simply call std::process::exit(0)
), then I will get a zero byte response with proper termination (i.e. FIN) in the case of the above code example (operating system: FreeBSD). In either case, a FIN might confuse network peers, as they see a properly terminated connection where in fact the program on the remote end crashed.
Looking into the documentation of TcpStream
, there seems no way to reset a connection (i.e. send an RST packet), not even explicitly.
I personally don't like the current behavior. I would prefer if a dropped TcpStream
results in a TCP RST by default, and only an explicit shutdown sends a FIN. To most other people, however, that might be unexpected and cause weird behavior (and break existing code). And maybe there are some more downsides I haven't thought of yet.
So I would propose to add a method to TcpStream
, which allows to set default behavior to TCP RST. On the operating system level under FreeBSD and Linux, this is achieved by calling setsockopt
with SO_LINGER
set to l_onoff = 1
and l_linger = 0
.
What do you think?