Skip to content

Commit f2fb7f3

Browse files
Merge pull request #545 from roc-lang/fix/tcp-listener-restart
Allow TCP listeners to restart on Unix
2 parents 4cf136c + 541ab20 commit f2fb7f3

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

‎platform/Tcp.roc‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ Tcp :: [].{
113113
## system for a free port, which [Listener.local_port!] reports. The
114114
## timeout covers hostname resolution.
115115
##
116+
## On Unix, a restarted server can bind again while connections from its
117+
## previous listener are still in TCP `TIME_WAIT`.
118+
##
116119
## ```roc
117120
## listener = Tcp.listen!("127.0.0.1", 0, Duration.seconds(5))?
118121
## port = listener.local_port!()?

‎src/listener.rs‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ fn listen(host: String, port: u16, timeout_nanos: u64) -> io::Result<Listener> {
3333
socket2::Type::STREAM,
3434
Some(socket2::Protocol::TCP),
3535
)?;
36+
// Allow a server restart while its previous connections are in TIME_WAIT.
37+
#[cfg(unix)]
38+
socket.set_reuse_address(true)?;
3639
#[cfg(windows)]
3740
exclusive_address(&socket)?;
3841
socket.bind(&address.into())?;
@@ -260,6 +263,38 @@ mod tests {
260263
assert!(matches!(listener.socket(), Err(ListenerError::Closed)));
261264
}
262265

266+
#[cfg(unix)]
267+
#[test]
268+
fn can_listen_again_while_previous_connection_is_in_time_wait() {
269+
use std::{io::Read, net::Shutdown};
270+
271+
let listener = listen("127.0.0.1".into(), 0, LONG).unwrap();
272+
let address = listener
273+
.socket()
274+
.unwrap()
275+
.local_addr()
276+
.unwrap()
277+
.as_socket()
278+
.unwrap();
279+
let mut client = TcpStream::connect(address).unwrap();
280+
let mut server = listener.accept(Some(LONG)).unwrap();
281+
server.set_nonblocking(false).unwrap();
282+
let timeout = Some(Duration::from_nanos(LONG));
283+
client.set_read_timeout(timeout).unwrap();
284+
server.set_read_timeout(timeout).unwrap();
285+
286+
// The server sends the first FIN and enters TIME_WAIT when it receives the client's FIN.
287+
let mut byte = [0];
288+
server.shutdown(Shutdown::Write).unwrap();
289+
assert_eq!(client.read(&mut byte).unwrap(), 0);
290+
client.shutdown(Shutdown::Write).unwrap();
291+
assert_eq!(server.read(&mut byte).unwrap(), 0);
292+
drop((server, client, listener));
293+
294+
listen("127.0.0.1".into(), address.port(), LONG)
295+
.expect("could not restart listener with a connection in TIME_WAIT");
296+
}
297+
263298
#[test]
264299
fn accept_without_a_timeout_waits_for_a_client() {
265300
let listener = listen("127.0.0.1".into(), 0, LONG).unwrap();

0 commit comments

Comments
 (0)