torrust-tracker
torrust-tracker copied to clipboard
Refactor `bit_torrent::tracker::udp::client::UdpClient` to return errors instead of panicking
The UDP Tracker Client struct contains some unwrap and panic calls.
pub struct UdpClient {
/// The socket to connect to
pub socket: Arc<UdpSocket>,
/// Timeout for sending and receiving packets
pub timeout: Duration,
}
impl UdpClient {
pub async fn bind(local_address: &str) -> Self {
let valid_socket_addr = local_address
.parse::<SocketAddr>()
.unwrap_or_else(|_| panic!("{local_address} is not a valid socket address"));
let socket = UdpSocket::bind(valid_socket_addr).await.unwrap();
Self {
socket: Arc::new(socket),
timeout: DEFAULT_TIMEOUT,
}
}
pub async fn connect(&self, remote_address: &str) {
let valid_socket_addr = remote_address
.parse::<SocketAddr>()
.unwrap_or_else(|_| panic!("{remote_address} is not a valid socket address"));
match self.socket.connect(valid_socket_addr).await {
Ok(()) => debug!("Connected successfully"),
Err(e) => panic!("Failed to connect: {e:?}"),
}
}
pub async fn send(&self, bytes: &[u8]) -> usize {
debug!(target: "UDP client", "sending {bytes:?} ...");
match time::timeout(self.timeout, self.socket.writable()).await {
Ok(writable_result) => match writable_result {
Ok(()) => (),
Err(e) => panic!("{}", format!("IO error waiting for the socket to become readable: {e:?}")),
},
Err(e) => panic!("{}", format!("Timeout waiting for the socket to become readable: {e:?}")),
};
match time::timeout(self.timeout, self.socket.send(bytes)).await {
Ok(send_result) => match send_result {
Ok(size) => size,
Err(e) => panic!("{}", format!("IO error during send: {e:?}")),
},
Err(e) => panic!("{}", format!("Send operation timed out: {e:?}")),
}
}
pub async fn receive(&self, bytes: &mut [u8]) -> usize {
debug!(target: "UDP client", "receiving ...");
match time::timeout(self.timeout, self.socket.readable()).await {
Ok(readable_result) => match readable_result {
Ok(()) => (),
Err(e) => panic!("{}", format!("IO error waiting for the socket to become readable: {e:?}")),
},
Err(e) => panic!("{}", format!("Timeout waiting for the socket to become readable: {e:?}")),
};
let size = match time::timeout(self.timeout, self.socket.recv(bytes)).await {
Ok(recv_result) => match recv_result {
Ok(size) => size,
Err(e) => panic!("{}", format!("IO error during send: {e:?}")),
},
Err(e) => panic!("{}", format!("Receive operation timed out: {e:?}")),
};
debug!(target: "UDP client", "{size} bytes received {bytes:?}");
size
}
}
That makes it impossible to catch those errors upstream. For example, the Tracker Checker can not use this UdpClient and continue its execution if a UDP Tracker request fails.
We should return the Result type and let the caller handle the problem.
This Client was introduced for testing purposes and later moved to production, so it lacks some important features like:
- Testing.
- Better error handling.