Skip to content
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

Handle duplicated requests #54

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Handle duplicated requests #54

wants to merge 6 commits into from

Conversation

wishawa
Copy link

@wishawa wishawa commented Oct 10, 2020

Fixes #49.

I tried editing the client.rs to send duplicated request. The server errored as expected.

But beyond that, I'm at lost on how to test this, or write automated tests for it.

I'm still a beginner in Rust, so if I'm doing anything wrong or unidiomatic, please do tell!

@wishawa
Copy link
Author

wishawa commented Oct 10, 2020

CI failure seems like our random_port hit a restricted port 🤔. Maybe use predefined list of ports for tests in the future?

@connorkuehl
Copy link
Owner

But beyond that, I'm at lost on how to test this, or write automated tests for it.

I think a good start would be defining a test in src/server.rs.

We can take advantage of the fact that the code is designed to separate the accepting of the request from actually handling the request. I.e., Server::serve returns a Handler struct which someone can call handle() on whenever they want.

The test could spawn the server, move it to another thread and have it start listening with Server::serve().

The test could then open a client socket and send two Rrqs in a row.

The test should assert that Server::handle returns an Ok(Handler) for the first request, and an Err for the second request.

It's important to not call the handle() method on the Handlers returned by the Server because we don't want it to complete and then unregister itself from the Server, otherwise the "duplicate" connection won't be detected.

CI failure seems like our random_port hit a restricted port 🤔. Maybe use predefined list of ports for tests in the future?

Nah. It's a rare enough occurrence that I can just retrigger it. If it starts becoming more of a problem I might just serialize the tests.

@wishawa
Copy link
Author

wishawa commented Oct 10, 2020

The tests implementations are a bit messy, with pieces copied from the actual client.rs edited to behave incorrectly.
One workaround is to put the tests in the client module, so we would be able to access the private fields and simulate incorrect behaviors from the real client code there.
But that would mean putting server tests in the client module... I'm not sure if that is a bad practice?

Or we could use proper code from the client module for all the normal behavior tests (which is 3 of the 4 I've written), and use the hand-rolled implementation for the duplicate request test alone. What do you think?

@PurpleMyst
Copy link
Contributor

You could look at my connection.rs tests (in #47) to see how to induce duplicated requests

Copy link
Contributor

@PurpleMyst PurpleMyst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple comments that came to me while reading the PR :>

src/server.rs Outdated
@@ -78,7 +94,18 @@ impl Server {
let addr = self.socket.local_addr()?.ip().to_string();
let bind_to = format!("{}:{}", addr, port);

Handler::new(bind_to, src_addr, direction, self.serve_dir.clone())
let active_clients = Arc::clone(&self.active_clients);
let callback: Callback = Some(Box::new(move || {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we avoid the Callback allocation + indirection by having a separate struct that holds the active_clients Arc and does the same thing as this callback? It's not like we ever expose Handler to the outside world and thus would ever have multiple callbacks. :>

src/server.rs Outdated
});
match server.serve() {
Err(e) => assert_eq!(e.kind(), io::ErrorKind::AddrNotAvailable),
_ => (),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test should fail if server.serve() does not return an error after the first request. You can do this by just panic!-ing in this branch.

src/server.rs Outdated
pub fn handle(mut self) -> Result<()> {
let callback = match self.callback.take() {
Some(l) => l,
None => Box::new(|| {}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a case in normal operation where this would be None? Our Handler is moved into Handler::handle, therefore we won't ever call it again.

Suggested change
None => Box::new(|| {}),
None => unreachable!(),

src/server.rs Outdated

assert_eq!(&exemplar[..], &res[..]);
}
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this block accomplish? We've already finished the previous request and dropped the socket by this point, so (as far as I understand the code) the src_addr would already be out of active_clients, right?

src/server.rs Outdated
h.handle().unwrap();
})));
}
for i in 0..n {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid having to have a Vec<Option<...>> by choosing to instead iterate over the Vec directly (instead of using indices), as that will move your elements out of the vec progressively

@PurpleMyst
Copy link
Contributor

You could also just move all the Callback-related code into the Handler itself to avoid having the Option<...> shenanigans and a separate struct

@wishawa
Copy link
Author

wishawa commented Oct 12, 2020

You could also just move all the Callback-related code into the Handler itself to avoid having the Option<...> shenanigans and a separate struct

But in that case, if the Handler error out before getting to the end, the callback would not be called and the TID would never be released.

@PurpleMyst
Copy link
Contributor

You can keep the logic inline in the handle method, that way if get or put error out you can still run the cleanup logic.

@connorkuehl
Copy link
Owner

I agree with @PurpleMyst. Let's drop the callback. Any structs that need access can just have a ClientsPool reference (Arc<Mutex>). It can be kept in the handle method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Server should ignore duplicate requests
3 participants