-
Notifications
You must be signed in to change notification settings - Fork 15
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
Add packet retransmission on timeout #47
base: main
Are you sure you want to change the base?
Conversation
Just wanted to let you know that this is on my radar. It's a bit of a larger PR so it might take me a little while longer to wrap my head around the changes and compile any feedback I have. Thanks! 🙂 |
Alright, I appreciate it. Take your time, I understand it's an hefty PR :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really fantastic work, @PurpleMyst! 😀
I have comments inline, but a summary:
- Move
RetransmissionConfig
into theconnection
module (theconnection
module will have to be madepub
iirc) - Implement
Default
for RetransmissionConfig. This way we can get rid of all theOption
stuff around the retransmission config as well as stop having to pass anOption
RetransmissionConfig in type constructors.- Construct the Server with the default config, and add a method on
Server
to let callers replace the default config. client::{Builder, Config}
are a similar story, but update the Builder to have a default RetransmissionConfig and give it a method for callers to overwrite with their own config.
- Construct the Server with the default config, and add a method on
- Reword the doc comment for
RetransmissionConfig
.
src/lib.rs
Outdated
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] | ||
pub struct RetransmissionConfig { | ||
/// How long should we wait for a reply before retransmitting the last packet? | ||
timeout: std::time::Duration, | ||
|
||
/// How many times should we retransmit the last packet? | ||
/// | ||
/// Note that this is the number of *retransmissions*, not transmissions, so | ||
/// this means that setting this to `Some(0)` means that the packet will still be | ||
/// sent once. | ||
/// | ||
/// If this is set to `None`, the packet will be retransmitted indefinitely. | ||
max_retransmissions: Option<usize>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's put this in in the connection
module, which will need to be made pub
iirc.
src/client.rs
Outdated
} | ||
|
||
impl Builder<New> { | ||
/// Generates a Transfer ID (a bind address & port) and opens a `UdpSocket` | ||
/// for this connection. | ||
pub fn new() -> Result<Self> { | ||
pub fn new(retransmission_config: Option<RetransmissionConfig>) -> Result<Self> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing this as an argument to this constructor, let's add a method to impl Builder<ConnectTo>
called with_retransmit(cfg: RetransmissionConfig) -> Result<Self>
and just replace the default RetransmissionConfig
.
src/client.rs
Outdated
|
||
/// The initial state for building a `Client`. | ||
pub struct New { | ||
socket: UdpSocket, | ||
retransmission_config: Option<RetransmissionConfig>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
retransmission_config: Option<RetransmissionConfig>, |
One of my later comments suggests moving this only into ConnectTo
and Client
.
src/client.rs
Outdated
@@ -24,6 +26,7 @@ pub struct New { | |||
pub struct ConnectTo { | |||
server: Vec<SocketAddr>, | |||
socket: UdpSocket, | |||
retransmission_config: Option<RetransmissionConfig>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
retransmission_config: Option<RetransmissionConfig>, | |
retransmission_config: RetransmissionConfig, |
Let's construct this with a default retransmission config; one of my later comments suggests adding a method to Builder<ConnectTo>
which will allow a caller to optionally replace the default retransmission config.
So, I think implementing Default for RetransmissionConfig
is a good first step here.
src/client.rs
Outdated
@@ -35,18 +38,23 @@ pub struct Builder<T> { | |||
pub struct Client { | |||
server: Vec<SocketAddr>, | |||
socket: UdpSocket, | |||
retransmission_config: Option<RetransmissionConfig>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
retransmission_config: Option<RetransmissionConfig>, | |
retransmission_config: RetransmissionConfig, |
See other comments -- general idea is to use a default retransmission config and let caller decide if they want to replace the default by calling a method giving us their preferred config.
|
||
#[test] | ||
fn test_put_retransmits_data() { | ||
const BOGUS_DATA: &[u8] = b"hey, look, listen"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha! 😀
src/server.rs
Outdated
pub fn new<A: ToSocketAddrs, P: AsRef<Path>>( | ||
bind_to: A, | ||
serve_from: P, | ||
retransmission_config: Option<RetransmissionConfig>, | ||
) -> Result<Self> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub fn new<A: ToSocketAddrs, P: AsRef<Path>>( | |
bind_to: A, | |
serve_from: P, | |
retransmission_config: Option<RetransmissionConfig>, | |
) -> Result<Self> { | |
pub fn new<A: ToSocketAddrs, P: AsRef<Path>>( | |
bind_to: A, | |
serve_from: P, | |
) -> Result<Self> { |
Similar feedback to Client
. Once RetransmissionConfig
implements Default
, let's just have this constructor use its default value.
src/server.rs
Outdated
pub fn random_port<A: AsRef<str>, P: AsRef<Path>>( | ||
ip_addr: A, | ||
serve_from: P, | ||
retransmission_config: Option<RetransmissionConfig>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub fn random_port<A: AsRef<str>, P: AsRef<Path>>( | |
ip_addr: A, | |
serve_from: P, | |
retransmission_config: Option<RetransmissionConfig>, | |
pub fn random_port<A: AsRef<str>, P: AsRef<Path>>( | |
ip_addr: A, | |
serve_from: P, |
Same feedback as above
src/server.rs
Outdated
) -> Result<(u16, Self)> { | ||
let mut rng = rand::thread_rng(); | ||
let port: u16 = rng.gen_range(MIN_PORT_NUMBER, u16::MAX); | ||
let bind_to = format!("{}:{}", ip_addr.as_ref(), port); | ||
|
||
Self::new(bind_to, serve_from).map(|server| (port, server)) | ||
Self::new(bind_to, serve_from, retransmission_config).map(|server| (port, server)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a new method on Server
fn with_transmission_config(&mut self, cfg: RetransmissionConfig)
and have it replace the default RetransmissionConfig
it was constructed with
src/lib.rs
Outdated
/// POD struct representing the configuration of the retransmission of packets | ||
// NB: this is a struct so that you can only specify max_retransmissions if you specify a time :> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reword this to be less technical (I.e., move away from phrases like "POD struct"
Maybe something like "Describes the retransmission behavior for a TFTP connection"
What should the default RetransmissionConfig do? Currently passing None turns on Retransmission indefinitely. |
It can behave the same with an internal enum enum Retransmit {
Infinite,
Finite(u64),
} Or something else of your devising if you find something you like better! |
Alright, I'll work on that. Are you sure that you want this in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to investigate ways to move more of the retransmission logic into types. The check_retransmissions
method has a number of side effects in addition to checking if we should retransmit. These side effects include updating the current_transmissions
variable as well as possibly writing to the socket.
Suppose we had an enum like this:
pub enum Retransmit {
/// Do not retransmit packets. Just error out.
Nil,
/// Retransmit packets a limited amount of times
Finite {
/// How many times should we retransmit?
limit: usize,
},
Infinite,
}
It could impl Iterator
and depending on the variant, it could help control the retransmissions. The implementation of the next()
method for this trait means that:
If it's Nil
, it can just return None
.
If it's Finite
it can decrement its limit and return Some(count)
(the actual value is insignificant, so long as it is a Some
and not a None
otherwise iteration would halt)
And if it's Infinite
then it will just always return Some
.
Then, the retransmission loops can be written as:
for _ in self.retransmission_config.retransmit.iter_mut() {
// if successful, break out, blah blah
}
Or something similar depending on how one will access the contents of the RetransmissionConfig
. I think this will help dispense with the check_retransmission
method and allow us to manage retransmissions without being so aware of different bits of state.
Thoughts?
pub struct New { | ||
socket: UdpSocket, | ||
} | ||
pub struct New(()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub struct New(()); | |
pub struct New; |
Why was the socket from this Builder step?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that we could set the RetransmissionConfig
at any time in the Builder
.
Also, the reason there's one private field is to avoid people outside the crate constructing the New
struct
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] | ||
pub enum RetransmissionConfig { | ||
/// Do not retransmit packets. Just error out. | ||
NoRetransmission, | ||
|
||
/// Retransmit packets indefinitely | ||
ForeverAfter { | ||
/// How long should we wait before retransmitting? | ||
timeout: std::time::Duration, | ||
}, | ||
|
||
/// Retransmit packets a limited amount of times | ||
NTimesAfter { | ||
/// How long should we wait before retransmitting? | ||
timeout: std::time::Duration, | ||
|
||
/// How many times should we retransmit? | ||
limit: std::num::NonZeroUsize, | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] | |
pub enum RetransmissionConfig { | |
/// Do not retransmit packets. Just error out. | |
NoRetransmission, | |
/// Retransmit packets indefinitely | |
ForeverAfter { | |
/// How long should we wait before retransmitting? | |
timeout: std::time::Duration, | |
}, | |
/// Retransmit packets a limited amount of times | |
NTimesAfter { | |
/// How long should we wait before retransmitting? | |
timeout: std::time::Duration, | |
/// How many times should we retransmit? | |
limit: std::num::NonZeroUsize, | |
}, | |
} | |
pub enum Retransmit { | |
/// Do not retransmit packets. Just error out. | |
Nil, | |
/// Retransmit packets a limited amount of times | |
Finite { | |
/// How many times should we retransmit? | |
limit: std::num::NonZeroUsize, | |
}, | |
Infinite, | |
} | |
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] | |
pub struct RetransmissionConfig { | |
timeout: std::time::Duration, | |
retransmit: Retransmit, | |
} |
Move this into its own submodule under the connection
submodule; it can be exported without making connection
pub
after all.
First off, I'm not sure what the difference between my enum representation and yours is once you add back in the timeout Secondly, I'm not sure I like the idea of using an The |
Co-authored-by: Connor Kuehl <[email protected]>
Co-authored-by: Connor Kuehl <[email protected]>
The suggested struct + enum representation doesn't duplicate the
I was just thinking out loud when I said
Have you experimented with an approach with fewer side effects? |
That's valid, but IMHO it doesn't matter that much because
I could move the incrementation of check_retransmisions out of the function itself, but we'd still need to increment it somewhere and remember to do so anytime we used the function. This way, it's baked in. |
Resolves #6