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

Make requests concurrently #13

Merged
merged 10 commits into from
Oct 5, 2024
Merged

Make requests concurrently #13

merged 10 commits into from
Oct 5, 2024

Conversation

xoen
Copy link
Collaborator

@xoen xoen commented Oct 4, 2024

This is related to Issue #7.

Spawn a Tokio task per request instead of making them sequentially.
Details about how the data flows back from the green threads into the format we wants are given in the commit message here - hopefully makes sense.

As mentioned in that commit message I may experiment with the result unwrapping further to see if I can get to something even simpler/more readable (you should have seen the original version of this 😅)

I've made some tweaks/refactorings. Explaination is given in the individual commit messages.
If you really prefer I can split the refactorings commits into its own PR and then have a PR only for the concurrent change.

`to_tuple()` was using unwrap instead of handling
the error, e.g. return an `Err(ApiError)` in this
case.

AFAICT `to_tuple()` is only used on date strings
coming from the API so it should be fine, but it
still makes sense to handle potentiall parsing errors.

NOTE: The `?` operator implicitly converts the given
value into the return error type via `From`/`Into`.
That's what the `#[from]` attribute is doing:
it's implementing the `From` trait to convert a
`chrono::ParseError` into an `ApiError`.
Both branches push into the `ranges` `Vec`.
By using the `?` operator to bubble up `chrono::ParseError`.
This is now possible because we convert those into `ApiError`.
This is a better name that conveys what it does and
it also avoid potential confusion with the standard
`parse()` method that uses the `FromStr` trait.
This is a common pattern in crates that return `Result` with a given
error. For example `std::io::Result` is a type alias to
`std::result::Result<T, std::io::Error>`.
The logic to get make the HTTP request, handle errors,
deserialise the response was common.

I've factored out this into its own generic function.
This will be more useful in the logic that unwrap the tokio tasks
results and gradually peal off the various layers of `Result`.
When requesting data for different date ranges make these requests
concurrently instead of making them sequentially.

Broadly speaking this is what's happening:
- spawn a Tokio task for each of these requests.
  - each task will return a `Result<Vec<IntensityForDate>>`
- wait for them to return using `try_join_all()`
  - this returns a `Result<Vec<Result<Vec<IntensityForDate>>>>`
- if it's an error (e.g. some of the task panicked) we return it
  using `?`
- we then have a `Vec<Result<Vec<IntensityForDate>>>`. One element
  for each of the requests' results.
- we "invert" that and turn it into `Result<Vec<Vec<IntensityForDate>>>`
  - either we return the first error or unwrap the list of tuples
- we now have a `Vec<Vec<IntensityForDate>>`, one element per response
  and each element is a `Vec<IntensityForDate>` (the list of tuples
  with date/intensity)
- we finally flatten this into a simpler `Vec<IntensityForDate>`

This is a bit more convoluted than I'd like but I hope it makes sense.

Some of the types in the variable declarations are there mainly
for readability sake given the levels of nesting but some of them
are there to help Rust figure out how to `collect()`.

**NOTE**: I'm still not 100% happy with this and I do wonder if the
unwrapping/error handling could be done more elegantly - possible.
Also, potentially there may be a way of doing this in a single bigger
step, I may experiment with it further to see if it turns out to be
a bit more readable.
@@ -30,6 +30,8 @@ pub enum ApiError {
Error(String),
}

pub type Result<T> = std::result::Result<T, ApiError>;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

One reason why I introduced this was because writing down the types when massaging the return values from the tokio tasks was getting a bit out of control and error prone.

@xoen xoen requested a review from jnioche October 4, 2024 18:27
@jnioche
Copy link
Owner

jnioche commented Oct 5, 2024

Spawn a Tokio task per request instead of making them sequentially.

it the number of requests needed is large, we might flood the API (unlikely though). Maybe later on we could limit the number of threads used.

If you really prefer I can split the refactorings commits into its own PR and then have a PR only for the concurrent change.

separating them would be extra work for now but maybe in the future let's aim to have separate PRs to make it easier to review

@jnioche
Copy link
Owner

jnioche commented Oct 5, 2024

we are now getting "The date range you have specified is greater than 14 days. Please select a smaller date range."
with
carbonintensity-api -s 2022-01-01 bs7

but are getting the expected values with the main branch

simplified pipeling that
- unwraps/bubble up tokio `JoinError`
- converts list of tasks Results into a single Result of tuples
- flatten the tuples (Vec of Vecs)
@xoen xoen marked this pull request as ready for review October 5, 2024 12:33
This reverts commit 111d09f.

It turns out the logic inside the if/else blocks was **not** the same.
This was a bug :)
@xoen
Copy link
Collaborator Author

xoen commented Oct 5, 2024

we are now getting "The date range you have specified is greater than 14 days. Please select a smaller date range."
with
carbonintensity-api -s 2022-01-01 bs7

but are getting the expected values with the main branch

Its-a me a bug :)!

I had a look and tried go back in time slowly and it turns out that this commit introduced a bug: 111d09f - I've reverted it and it now seems to work.

PS: Wow, it's amazing how much of a time difference there is between making these requests concurrently and sequentially :)

@jnioche jnioche added this to the 0.3.0 milestone Oct 5, 2024
@jnioche jnioche added the enhancement New feature or request label Oct 5, 2024
@jnioche
Copy link
Owner

jnioche commented Oct 5, 2024

implements #7

@jnioche
Copy link
Owner

jnioche commented Oct 5, 2024

PS: Wow, it's amazing how much of a time difference there is between making these requests concurrently and sequentially :)

yes! 10s vs 177s

@jnioche jnioche merged commit 9d67439 into main Oct 5, 2024
1 check passed
@jnioche jnioche deleted the concurrently branch October 5, 2024 16:14
@jnioche
Copy link
Owner

jnioche commented Oct 5, 2024

Fab, thanks @xoen

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

Successfully merging this pull request may close these issues.

2 participants