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

Implement retryable calls #98

Merged
merged 5 commits into from
Sep 25, 2024

Conversation

ValuedMammal
Copy link
Contributor

@ValuedMammal ValuedMammal commented Sep 7, 2024

Based on #93 the PR implements retryable calls for request failure due to too many requests (429) or service unavailable (503).

Inspired by #71
h/t @e1a0a0ea

Notes

I've added the field max_retries to the Builder. max_retries is also added to each of AsyncClient, BlockingClient. I added the dependency async-std in order to have async sleep.

Instead of implementing a trait on the Request type as in #71, the approach is to add a method on the client that sends a get request to a url and returns the response after allowing for retries. I tested on the bdk wallet_esplora_* example crates against https://blockstream.info/testnet/api and it seemed to resolve the 429 issue.

@coveralls
Copy link

coveralls commented Sep 7, 2024

Pull Request Test Coverage Report for Build 10962612990

Details

  • 137 of 175 (78.29%) changed or added relevant lines in 3 files are covered.
  • 2 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.6%) to 88.071%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/lib.rs 1 5 20.0%
src/blocking.rs 19 30 63.33%
src/async.rs 117 140 83.57%
Files with Coverage Reduction New Missed Lines %
src/async.rs 2 80.54%
Totals Coverage Status
Change from base Build 10748775132: 0.6%
Covered Lines: 1041
Relevant Lines: 1182

💛 - Coveralls

- apply some standard on `Cargo.toml` deps.
- minor docstring improvements, and fix missing docstrings.
- remove duplicated HTTP client code for handling GET and POST requests.
- adds a few new methods to `AsyncClient` implementation, the new
  methods are responsible to handle common HTTP requests and parsing. It
  was previously duplicated throughout the Esplora API implementation,
  but now it follows the same approach already implemented for
  blocking client (`BlockingClient`).
src/blocking.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@oleonardolima oleonardolima left a comment

Choose a reason for hiding this comment

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

Concept ACK

Copy link
Collaborator

@oleonardolima oleonardolima left a comment

Choose a reason for hiding this comment

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

It's looking good! Thanks for working on this, I just left a few comments and nits.

I wonder if we should try to add a test for the retry behavior, but unsure if it's worth it.

src/async.rs Outdated
Comment on lines 437 to 438
if attempts < self.max_retries
&& RETRYABLE_ERROR_CODES.contains(&resp.status().as_u16()) =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: You can apply the same approach here and extract it to is_retryable_status too.

src/async.rs Outdated
let mut attempts = 0;

loop {
match self.client.get(url).send().await {
Copy link
Collaborator

@oleonardolima oleonardolima Sep 20, 2024

Choose a reason for hiding this comment

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

nit: You can also just propagate the error here, right? (edit: unsure if an if would be a better style)

P.S.: The same applies to the blocking function.

Cargo.toml Outdated
@@ -17,6 +17,7 @@ name = "esplora_client"
path = "src/lib.rs"

[dependencies]
async-std = "1.13.0"
Copy link
Collaborator

Choose a reason for hiding this comment

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

It should be an optional dependency, and only available when using the async feature.

Also, I don't think we need all the features in its default features, and only the std feature, is that right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried with only std feature and it seems that the task module is only available with "default"

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, looks like it's behind the default feature here using their cfg_default! macro.

@oleonardolima oleonardolima added the enhancement New feature or request label Sep 20, 2024
@ValuedMammal
Copy link
Contributor Author

ValuedMammal commented Sep 20, 2024

  • Made async-std an optional dep
  • Small improvements to get_with_retry

@oleonardolima I haven't come up with a test for the retry behavior, although we can at least be confident that the methods that call get_with_retry still return a response

I'm testing against https://blockstream.info/api and now seeing some "500 - Internal Server Error", so I'm considering adding this to RETRYABLE_ERROR_CODES.

Copy link
Collaborator

@oleonardolima oleonardolima left a comment

Choose a reason for hiding this comment

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

ACK 693af39

nit: I'd just update the last commit message to something like: refactor(async,blocking): `get_with_retry` fn to follow the standard.

src/async.rs Outdated Show resolved Hide resolved
This change also adds a new field `max_retries` to each of
`Builder`, `AsyncClient`, and `BlockingClient` that defines
the maximum number of times we may retry a request.
Copy link
Collaborator

@oleonardolima oleonardolima left a comment

Choose a reason for hiding this comment

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

ACK b96270e

Copy link
Member

@notmandatory notmandatory left a comment

Choose a reason for hiding this comment

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

tACK b96270e

I tested with example_esplora on mainnet (with gap-limit 100) and testnet. In both cases tests failed with the master branch version of this code and works with the fixes in this PR.

@notmandatory notmandatory merged commit 1542262 into bitcoindevkit:master Sep 25, 2024
25 checks passed

/// Sends a GET request to the given `url`, retrying failed attempts
/// for retryable error codes until max retries hit.
pub fn get_with_retry(&self, url: &str) -> Result<Response, Error> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, I don't think it was intended for this to be pub.

@ValuedMammal ValuedMammal deleted the feat/retryable branch September 25, 2024 13:16
ValuedMammal added a commit that referenced this pull request Sep 26, 2024
bfaa9ae fix(blocking): `get_with_retry` is private method (valued mammal)

Pull request description:

  Clean up after #98 which accidentally exposed `get_with_retry` publicly for the blocking client.

ACKs for top commit:
  oleonardolima:
    ACK bfaa9ae

Tree-SHA512: 33abd34e919e27069107947c34915b9de3547ab1d78291841f2052552abd23fd59621ee6ae3ed9bd8e3eda608c2e5b9285529df1dcba8a4a1705ca1f1491c136
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
Archived in project
Development

Successfully merging this pull request may close these issues.

4 participants