-
Notifications
You must be signed in to change notification settings - Fork 0
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 concurrent requests #8
base: use_debug_flag
Are you sure you want to change the base?
Conversation
7971a5b
to
9d8aaa0
Compare
…ion layout pattern.
… --debug CLI flag.
…e_debug_flag Split app logic and use debug flag for response output
src/lib.rs
Outdated
Err(e) => { | ||
match cli.debug { | ||
match debug { |
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.
Same comment as above, this can be an if statement instead of a pattern matching statement
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 can't find where it would be defined in the code (maybe part of the clap
?) but can the description for -i be updated to include information on what purpose it serves in addition to the default value?
Indeed it can, just adding a comment above the element definition in the structure provides the printed help for this flag, e.g.: https://github.com/Jim-Hodapp-Coaching/ambi_mock_client/blob/master/src/lib.rs#L27 |
Oh nice. I saw the comment above the one line and wondered if it was clever enough to do that. Thanks for the explanation |
Absolutely, |
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 love to see the addition of some kind of identifier to each thread that you spawn, so we can see in debug output which response corresponds to which thread.
For example, even a simple index or assigned thread id could be interesting and helpful.
I won't block merging this PR but consider it a strong preference to see it added, or I'd be ok with a follow-on PR.
Thank you all very much for your reviews. Learning a ton from the conversation. I added a commit 59aa0e0 Which adds a few changes:
I'd like to write some tests for the public methods I added but I wanted to give ya'll a chance to look this over and see if it's something we think can accomplish what we want. Mostly just hoping to zero in on a decent extendable interface for the |
src/lib.rs
Outdated
} | ||
|
||
fn print_to_stderr(&self) { | ||
if self.debug { |
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.
This is how we accomplish debug output vs non-debug
src/lib.rs
Outdated
} | ||
} | ||
|
||
impl fmt::Display for Output { |
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.
This trait is how we accomplish the customization for non-debug output
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.
A wonderful contribution, thank you for this Caleb. I left several thoughts and ideas for how to evolve this even further. Take or leave whatever you want.
@@ -27,6 +28,10 @@ pub struct Cli { | |||
/// Turns verbose console debug output on | |||
#[clap(short, long)] | |||
pub debug: bool, | |||
|
|||
// Make int number of concurrent requests | |||
#[clap(short, long, default_value_t = 1)] |
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.
Is there an upper limit to how many concurrent requests we want to allow for? Perhaps adding CLI interface data check on that would be in order. For example, do we really want to allow for 65k simultaneous threads, or at least without having tested it well? :)
} | ||
|
||
impl Output { | ||
pub fn 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.
I wonder if we could have one static instance of Output
instead of having to call new()
each time. That way it'd be a very lightweight operation both in execution time and in memory allocation. So it wouldn't be on the heap then, it'd be in a special section of memory called the Block Starting Symbol (BSS). These become global instances and are meant to be shared and used in different portions of an application.
Happy to discuss this more, just wanted to plant the idea.
src/lib.rs
Outdated
@@ -56,6 +61,74 @@ impl Reading { | |||
} | |||
} | |||
|
|||
#[derive(Debug)] | |||
struct Output { |
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.
One idea for you - what if we called this Log
, or Logger
or even more specific ResponseLog
\ ResponseLogger
instead of Output
? There may even be naming convention that's common in other Rust programs here (and perhaps that is Output
and I just aren't aware of this).
src/lib.rs
Outdated
} | ||
|
||
pub fn is_error(&self) -> bool { | ||
self.error.is_some() |
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 absolutely love the use of Rust's Option
concept here, fits perfectly in line with success vs error cases.
src/lib.rs
Outdated
let handler = spawn(move || | ||
match send_request(URL, Client::new()) { | ||
Ok(response) => { | ||
Output::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.
In reference to my above suggestion for a different name and a static version, this then might look something like:
ResponseLog.print(
None,
Some(response),
std::thread::current().id()
);
The String
could become implicit because it's a specific enough kind of logger type, and you could initialize the static instance of it with debug
once instead of needing to pass it along every time.
One other thought: could the None and Some become a literal Option
that you pass to print()
in which it could figure out whether it's a success message vs an error message?
Just an idea, curious what you think.
Co-authored-by: Jim Hodapp <[email protected]>
…ing/ambi_mock_client into add_concurrent_requests
@jhodapp @glynos I added a commit 6c896df which adds a One interesting thing I've been finding is it's difficult to test anything that refers directly to types that are owned by external crates. My first instinct is to use dependency injection for example: defining #[derive(Debug, PartialEq)]
struct ResponseLog<'a, W: Write> {
debug: bool,
writer: &'a mut W,
} and then pass a Vector in test to be able to prove that writing happens. fn response_log_prints() {
let mut writer = Vec::new();
let result = "123";
let mut response_log = ResponseLog::new(false, &mut writer);
let expected = result.as_bytes().to_vec();
response_log.print(result);
assert_eq!(writer, expected)
} It seems like the best thing to do would be to wrap any external dependencies in your own types and test that way but that's also a lot of code. I'm curious how this is best handled in Rust. Specifically in testing. |
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 great stuff Caleb, I think we're getting closer to this being a very strong and helpful contribution to the mock client! I left a few suggestions inline that I hope are helpful.
} | ||
|
||
#[derive(Debug, PartialEq)] | ||
struct ResponseLog<'a, W: Write> { |
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.
This is great Caleb. Do you have a specific reason to not name this more broadly and only use it for logging responses? Could we use this as our logger more widely? Maybe you could share your thoughts on naming it Response
specifically?
} | ||
|
||
impl<'a, W: Write> ResponseLog<'a, W> { | ||
pub fn new(debug: bool, writer: &'a mut W) -> 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.
Have you considered a static lifetime that is also trait bound? Here's an example of what I mean: https://doc.rust-lang.org/rust-by-example/scope/lifetime/static_lifetime.html#trait-bound
Some(response), | ||
std::thread::current().id(), | ||
); | ||
ResponseLog::new(debug, &mut std::io::stdout()).print(result) |
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.
Whether the use of new()
remains or you decide to take my advice and switch to a static instance of the logger, I think it makes ergonomic sense to separate instantiation of ResponseLog
from using it print()
. And then you can only create a single instance of it one time and not have to repeat instantiating it.
Description
This PR adds a
-i, --int <INT>
flag to the CLI for sending INT number of concurrent requests to Ambi at a time.