chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

1
vendor/backon/.cargo-checksum.json vendored Normal file
View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"9d38841fd01de84652b10cc2e6f29a534fbb1614d86f0dd9e16c049343e16256","Cargo.lock":"25d0250c54d2066dac9e37825413ff488a4769f4b588bc3a5e42507370a4c4e4","Cargo.toml":"ed4f4040254a5d5778b8bf91b62aff9776ef1f5cf058a15cded834a7e9b4fa35","Cargo.toml.orig":"f06fe500d488093ac85c5c74c33ad6f404e5383b80976cb78c6e9c8fcb0cc48b","LICENSE":"3e16583bb85121134f781623f64f72481a617f3c295182e60728804b05f0aeda","README.md":"ef47084f7ef620e2858016e90083e27284d094ce24c9339fd7452a8d89db6bed","src/backoff/api.rs":"e31b41894c614872d74abb830ba3b658d1d0d2c503b07219f97c75f18ce1d0bb","src/backoff/constant.rs":"49103def02d7253d882a857faeb79f7d85baf08b3176f7ab155a3e829d503004","src/backoff/exponential.rs":"52f76a125bb2e8c0f53571e2b55d9949be776fc2c81930e19b361e70f3e0e587","src/backoff/fibonacci.rs":"5df9157c1cff14ae880ddd6e84a5ee2c6d0869bf778c68a06ccba5a4fc75db92","src/backoff/mod.rs":"91f713bb8add53a552f142ccdabb802cd482ef204cb7d24178d1fb5ebcc54c89","src/blocking_retry.rs":"801e7281212e00375ed9a61d05389abc0f2ca89813d5e02dff6051bcc374cd00","src/blocking_retry_with_context.rs":"bbc20b4ca4803e12501c5e27ba85b543b827ffb63bc21fda47f4c217a3f25898","src/blocking_sleep.rs":"576bc0f38ba238225ee783b584f7ae2df08c61e6faf7132bb3972f9fc073188d","src/docs/examples/basic.md":"f2e25220e6f6306abf430b25052b38e18e9b324935e9699ec0aa3ec9bc8870bb","src/docs/examples/closure.md":"dbc13ee45ae515ea102e200b168e6f59e2f0ae91f0661ed395194bc251975176","src/docs/examples/custom_sleeper.md":"e3e6ff111a983c9b84adb70ef495d8c5230dd31b68a7eefc29301e0b8b7e6878","src/docs/examples/inside_mut_self.md":"422ed6cc321f1eb2054f23e62b1ec46b359e2f68050318032e48f2db2d6e06da","src/docs/examples/mod.rs":"ac803a7f003414b009b5399aea819dd73c65ea776efc77093826d1b9c204b987","src/docs/examples/retry_after.md":"07be35e8a6691f46a68705a65a186a6a752f29dc4cec616efd147b2f845d0ad1","src/docs/examples/sqlx.md":"1c928ffa099741866e8231af841c67408fdc3c533c50d3e1be731b655791c6cb","src/docs/examples/wasm32_basic.md":"468b43870b2603ccce3a3eb088768023c7537300ad31ed92be3204d1da43ff86","src/docs/examples/with_args.md":"521c04f02842e56fae9ab87b04da648941cf07a11bbfcc7c2d99bc5764be37c2","src/docs/examples/with_mut_self.md":"5c26971dc1af9c708d7486bc796ca03f80eb7a03bd5d1bd3ebf5a55a44fa09e8","src/docs/examples/with_self.md":"c2207770a3e9b5ec22897c6fd3fd7f69aa241b861cf0f8f6d5c739fd50905a8f","src/docs/examples/with_specific_error.md":"5c11a478cd447a1c83abe642546945349cc4b77819b58d8763f432538db6576e","src/docs/mod.rs":"f5bda25917354e935fbfd9ba96dbe8da0abe080b453f329648ca23aa3aa3681f","src/embassy_timer_sleep.rs":"be72e74b6a0c68c81f214120843003e42ae188d739d92fa2559e4e4c061dabd6","src/lib.rs":"22700bf219a32211fa9907ba95181987bdd48465306a01dd5fa6bfe48014598b","src/retry.rs":"8deeb3f2096bf47089827dc054eeb9a6fdc125c8d19fc1ab559cbb0e219a3ded","src/retry_with_context.rs":"5534fceb81984d6411c9dbcd268b411f36cce0777a1ab8e36d12aa9458868f32","src/sleep.rs":"154e2080c61779dd1019ca0b960a9a61ea05e1567174e7707efcf293ecf93b8a"},"package":"cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef"}

6
vendor/backon/.cargo_vcs_info.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "e24dc92742e201f7320868d46a12ff59eb82ef72"
},
"path_in_vcs": "backon"
}

2746
vendor/backon/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

118
vendor/backon/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,118 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
rust-version = "1.85"
name = "backon"
version = "1.6.0"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Make retry like a built-in feature provided by Rust."
documentation = "https://docs.rs/backon"
readme = "README.md"
license = "Apache-2.0"
repository = "https://github.com/Xuanwo/backon"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
targets = [
"x86_64-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-pc-windows-msvc",
"wasm32-unknown-unknown",
]
[features]
default = [
"std",
"std-blocking-sleep",
"tokio-sleep",
"gloo-timers-sleep",
]
embassy-sleep = ["embassy-time"]
futures-timer-sleep = ["futures-timer"]
gloo-timers-sleep = ["gloo-timers/futures"]
std = ["fastrand/std"]
std-blocking-sleep = []
tokio-sleep = ["tokio/time"]
[lib]
name = "backon"
path = "src/lib.rs"
[dependencies.embassy-time]
version = "0.5"
optional = true
[dependencies.fastrand]
version = "2"
default-features = false
[dev-dependencies.anyhow]
version = "1"
[dev-dependencies.reqwest]
version = "0.12"
[dev-dependencies.spin]
version = "0.10.0"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.futures-timer]
version = "3.0.3"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "1"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.sqlx]
version = "0.8.0"
features = [
"runtime-tokio",
"sqlite",
]
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.tokio]
version = "1"
features = [
"time",
"rt",
"macros",
"sync",
"rt-multi-thread",
]
[target.'cfg(target_arch = "wasm32")'.dependencies.futures-timer]
version = "3.0.3"
features = ["gloo-timers"]
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.gloo-timers]
version = "0.3"
optional = true
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.tokio]
version = "1"
features = [
"macros",
"rt",
"sync",
]
default-features = false
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.wasm-bindgen-test]
version = "0.3"

201
vendor/backon/LICENSE vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2021 Datafuse Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

97
vendor/backon/README.md vendored Normal file
View File

@@ -0,0 +1,97 @@
# BackON   [![Build Status]][actions] [![Latest Version]][crates.io] [![](https://img.shields.io/discord/1111711408875393035?logo=discord&label=discord)](https://discord.gg/8ARnvtJePD)
[Build Status]: https://img.shields.io/github/actions/workflow/status/Xuanwo/backon/ci.yml?branch=main
[actions]: https://github.com/Xuanwo/backon/actions?query=branch%3Amain
[Latest Version]: https://img.shields.io/crates/v/backon.svg
[crates.io]: https://crates.io/crates/backon
<img src="https://raw.githubusercontent.com/Xuanwo/backon/main/.github/assets/logo.jpeg" alt="BackON" width="38.2%"/>
Make **retry** like a built-in feature provided by Rust.
- **Simple API**: Native feel: `your_fn.retry(ExponentialBuilder::default()).await`.
- **Sync & Async**: Supports both blocking and async operations seamlessly.
- **Precise Control**: Define when to retry and get notified via [`when`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.when) and [`notify`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.notify).
- **Custom Strategies**: Use built-in backoff strategies (exponential, constant) or define custom ones. Also supports dynamic backoff, such as using the HTTP `Retry-After` header.
- **Cross-Platform**: Works everywhere Rust does, including `wasm` & `no-std`.
---
## Quick Start
For more examples, check out the [examples](https://docs.rs/backon/latest/backon/docs/examples/index.html).
### Retry an async function.
```rust
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;
async fn fetch() -> Result<String> {
Ok("hello, world!".to_string())
}
#[tokio::main]
async fn main() -> Result<()> {
let content = fetch
// Retry with exponential backoff
.retry(ExponentialBuilder::default())
// Sleep implementation, required if no feature has been enabled
.sleep(tokio::time::sleep)
// When to retry
.when(|e| e.to_string() == "EOF")
// Notify when retrying
.notify(|err: &anyhow::Error, dur: Duration| {
println!("retrying {:?} after {:?}", err, dur);
})
.await?;
println!("fetch succeeded: {}", content);
Ok(())
}
```
### Retry a blocking function.
```rust
use anyhow::Result;
use backon::BlockingRetryable;
use backon::ExponentialBuilder;
fn fetch() -> Result<String> {
Ok("hello, world!".to_string())
}
fn main() -> Result<()> {
let content = fetch
// Retry with exponential backoff
.retry(ExponentialBuilder::default())
// Sleep implementation, required if no feature has been enabled
.sleep(std::thread::sleep)
// When to retry
.when(|e| e.to_string() == "EOF")
// Notify when retrying
.notify(|err: &anyhow::Error, dur: Duration| {
println!("retrying {:?} after {:?}", err, dur);
})
.call()?;
println!("fetch succeeded: {}", content);
Ok(())
}
```
## Contributing
Check out the [CONTRIBUTING.md](./CONTRIBUTING.md) guide for more details on getting started with contributing to this
project.
## Getting help
Submit [issues](https://github.com/Xuanwo/backon/issues/new/choose) for bug report or asking questions
in [discussion](https://github.com/Xuanwo/backon/discussions/new?category=q-a).
## License
Licensed under <a href="./LICENSE">Apache License, Version 2.0</a>.

50
vendor/backon/src/backoff/api.rs vendored Normal file
View File

@@ -0,0 +1,50 @@
use core::time::Duration;
/// Backoff is an [`Iterator`] that returns [`Duration`].
///
/// - `Some(Duration)` indicates the caller should `sleep(Duration)` and retry the request.
/// - `None` indicates the limits have been reached, and the caller should return the current error instead.
pub trait Backoff: Iterator<Item = Duration> + Send + Sync + Unpin {}
impl<T> Backoff for T where T: Iterator<Item = Duration> + Send + Sync + Unpin {}
/// BackoffBuilder is utilized to construct a new backoff.
pub trait BackoffBuilder: Send + Sync + Unpin {
/// The associated backoff returned by this builder.
type Backoff: Backoff;
/// Construct a new backoff using the builder.
fn build(self) -> Self::Backoff;
}
impl<B: Backoff> BackoffBuilder for B {
type Backoff = B;
fn build(self) -> Self::Backoff {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ConstantBuilder;
use crate::ExponentialBuilder;
use crate::FibonacciBuilder;
fn test_fn_builder(b: impl BackoffBuilder) {
let _ = b.build();
}
#[test]
fn test_backoff_builder() {
test_fn_builder([Duration::from_secs(1)].into_iter());
// Just for test if user can keep using &XxxBuilder.
#[allow(clippy::needless_borrows_for_generic_args)]
{
test_fn_builder(&ConstantBuilder::default());
test_fn_builder(&FibonacciBuilder::default());
test_fn_builder(&ExponentialBuilder::default());
}
}
}

235
vendor/backon/src/backoff/constant.rs vendored Normal file
View File

@@ -0,0 +1,235 @@
use core::time::Duration;
use crate::backoff::BackoffBuilder;
/// ConstantBuilder is used to create a [`ConstantBackoff`], providing a steady delay with a fixed number of retries.
///
/// # Default
///
/// - delay: 1s
/// - max_times: 3
///
/// # Examples
///
/// ```no_run
/// use anyhow::Result;
/// use backon::ConstantBuilder;
/// use backon::Retryable;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch.retry(ConstantBuilder::default()).await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
#[derive(Debug, Clone, Copy)]
pub struct ConstantBuilder {
delay: Duration,
max_times: Option<usize>,
jitter: bool,
seed: Option<u64>,
}
impl Default for ConstantBuilder {
fn default() -> Self {
Self::new()
}
}
impl ConstantBuilder {
/// Create a new `ConstantBuilder` with default values.
pub const fn new() -> Self {
Self {
delay: Duration::from_secs(1),
max_times: Some(3),
jitter: false,
seed: None,
}
}
/// Set the delay for the backoff.
pub const fn with_delay(mut self, delay: Duration) -> Self {
self.delay = delay;
self
}
/// Set the maximum number of attempts to be made.
pub const fn with_max_times(mut self, max_times: usize) -> Self {
self.max_times = Some(max_times);
self
}
/// Enable jitter for the backoff.
///
/// Jitter is a random value added to the delay to prevent a thundering herd problem.
pub const fn with_jitter(mut self) -> Self {
self.jitter = true;
self
}
/// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
/// Set no max times for the backoff.
///
/// The backoff will not stop by itself.
///
/// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._
pub const fn without_max_times(mut self) -> Self {
self.max_times = None;
self
}
}
impl BackoffBuilder for ConstantBuilder {
type Backoff = ConstantBackoff;
fn build(self) -> Self::Backoff {
ConstantBackoff {
delay: self.delay,
max_times: self.max_times,
attempts: 0,
jitter: self.jitter,
rng: if let Some(seed) = self.seed {
fastrand::Rng::with_seed(seed)
} else {
#[cfg(feature = "std")]
let rng = fastrand::Rng::new();
#[cfg(not(feature = "std"))]
let rng = fastrand::Rng::with_seed(super::RANDOM_SEED);
rng
},
}
}
}
impl BackoffBuilder for &ConstantBuilder {
type Backoff = ConstantBackoff;
fn build(self) -> Self::Backoff {
(*self).build()
}
}
/// ConstantBackoff offers a consistent delay with a limited number of retries.
///
/// This backoff strategy is constructed by [`ConstantBuilder`].
#[doc(hidden)]
#[derive(Debug)]
pub struct ConstantBackoff {
delay: Duration,
max_times: Option<usize>,
attempts: usize,
jitter: bool,
rng: fastrand::Rng,
}
impl Iterator for ConstantBackoff {
type Item = Duration;
fn next(&mut self) -> Option<Self::Item> {
let mut delay = || match self.jitter {
true => self.delay + self.delay.mul_f32(self.rng.f32()),
false => self.delay,
};
match self.max_times {
None => Some(delay()),
Some(max_times) => {
if self.attempts >= max_times {
None
} else {
self.attempts += 1;
Some(delay())
}
}
}
}
}
#[cfg(test)]
mod tests {
use core::time::Duration;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
use super::*;
const TEST_BUILDER: ConstantBuilder = ConstantBuilder::new()
.with_delay(Duration::from_secs(2))
.with_max_times(5)
.with_jitter();
#[test]
fn test_constant_default() {
let mut it = ConstantBuilder::default().build();
assert_eq!(Some(Duration::from_secs(1)), it.next());
assert_eq!(Some(Duration::from_secs(1)), it.next());
assert_eq!(Some(Duration::from_secs(1)), it.next());
assert_eq!(None, it.next());
}
#[test]
fn test_constant_with_delay() {
let mut it = ConstantBuilder::default()
.with_delay(Duration::from_secs(2))
.build();
assert_eq!(Some(Duration::from_secs(2)), it.next());
assert_eq!(Some(Duration::from_secs(2)), it.next());
assert_eq!(Some(Duration::from_secs(2)), it.next());
assert_eq!(None, it.next());
}
#[test]
fn test_constant_with_times() {
let mut it = ConstantBuilder::default().with_max_times(1).build();
assert_eq!(Some(Duration::from_secs(1)), it.next());
assert_eq!(None, it.next());
}
#[test]
fn test_constant_with_jitter() {
let mut it = ConstantBuilder::default().with_jitter().build();
let dur = it.next().unwrap();
fastrand::seed(7);
assert!(dur > Duration::from_secs(1));
}
#[test]
fn test_constant_without_max_times() {
let mut it = ConstantBuilder::default().without_max_times().build();
for _ in 0..10_000 {
assert_eq!(Some(Duration::from_secs(1)), it.next());
}
}
// allow assertions on constants because they are not optimized out by unit tests
#[allow(clippy::assertions_on_constants)]
#[test]
fn test_constant_const_builder() {
assert_eq!(TEST_BUILDER.delay, Duration::from_secs(2));
assert_eq!(TEST_BUILDER.max_times, Some(5));
assert!(TEST_BUILDER.jitter);
}
}

461
vendor/backon/src/backoff/exponential.rs vendored Normal file
View File

@@ -0,0 +1,461 @@
use core::time::Duration;
use crate::backoff::BackoffBuilder;
/// ExponentialBuilder is used to construct an [`ExponentialBackoff`] that offers delays with exponential retries.
///
/// # Default
///
/// - jitter: false
/// - factor: 2
/// - min_delay: 1s
/// - max_delay: 60s
/// - max_times: 3
///
/// # Examples
///
/// ```no_run
/// use anyhow::Result;
/// use backon::ExponentialBuilder;
/// use backon::Retryable;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch.retry(ExponentialBuilder::default()).await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
#[derive(Debug, Clone, Copy)]
pub struct ExponentialBuilder {
jitter: bool,
factor: f32,
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
total_delay: Option<Duration>,
seed: Option<u64>,
}
impl Default for ExponentialBuilder {
fn default() -> Self {
Self::new()
}
}
impl ExponentialBuilder {
/// Create a new `ExponentialBuilder` with default values.
pub const fn new() -> Self {
Self {
jitter: false,
factor: 2.0,
min_delay: Duration::from_secs(1),
max_delay: Some(Duration::from_secs(60)),
max_times: Some(3),
total_delay: None,
seed: None,
}
}
/// Enable jitter for the backoff.
///
/// When jitter is enabled, [`ExponentialBackoff`] will add a random jitter within `(0, current_delay)`
/// to the current delay.
pub const fn with_jitter(mut self) -> Self {
self.jitter = true;
self
}
/// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
/// Set the factor for the backoff.
///
/// Note: Having a factor less than `1.0` does not make any sense as it would create a
/// smaller negative backoff.
pub const fn with_factor(mut self, factor: f32) -> Self {
self.factor = factor;
self
}
/// Set the minimum delay for the backoff.
pub const fn with_min_delay(mut self, min_delay: Duration) -> Self {
self.min_delay = min_delay;
self
}
/// Set the maximum delay for the backoff.
///
/// The delay will not increase if the current delay exceeds the maximum delay.
pub const fn with_max_delay(mut self, max_delay: Duration) -> Self {
self.max_delay = Some(max_delay);
self
}
/// Set no maximum delay for the backoff.
///
/// The delay will keep increasing.
///
/// _The delay will saturate at `Duration::MAX` which is an **unrealistic** delay._
pub const fn without_max_delay(mut self) -> Self {
self.max_delay = None;
self
}
/// Set the maximum number of attempts for the current backoff.
///
/// The backoff will stop if the maximum number of attempts is reached.
pub const fn with_max_times(mut self, max_times: usize) -> Self {
self.max_times = Some(max_times);
self
}
/// Set no maximum number of attempts for the current backoff.
///
/// The backoff will not stop by itself.
///
/// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._
pub const fn without_max_times(mut self) -> Self {
self.max_times = None;
self
}
/// Set the total delay for the backoff.
///
/// The backoff will stop yielding sleep durations once the cumulative sleep time
/// plus the next sleep duration would exceed `total_delay`.
pub const fn with_total_delay(mut self, total_delay: Option<Duration>) -> Self {
self.total_delay = total_delay;
self
}
}
impl BackoffBuilder for ExponentialBuilder {
type Backoff = ExponentialBackoff;
fn build(self) -> Self::Backoff {
ExponentialBackoff {
jitter: self.jitter,
rng: if let Some(seed) = self.seed {
fastrand::Rng::with_seed(seed)
} else {
#[cfg(feature = "std")]
let rng = fastrand::Rng::new();
#[cfg(not(feature = "std"))]
let rng = fastrand::Rng::with_seed(super::RANDOM_SEED);
rng
},
factor: self.factor,
min_delay: self.min_delay,
max_delay: self.max_delay,
max_times: self.max_times,
current_delay: None,
attempts: 0,
cumulative_delay: Duration::ZERO,
total_delay: self.total_delay,
}
}
}
impl BackoffBuilder for &ExponentialBuilder {
type Backoff = ExponentialBackoff;
fn build(self) -> Self::Backoff {
(*self).build()
}
}
/// ExponentialBackoff provides a delay with exponential retries.
///
/// This backoff strategy is constructed by [`ExponentialBuilder`].
#[doc(hidden)]
#[derive(Debug)]
pub struct ExponentialBackoff {
jitter: bool,
rng: fastrand::Rng,
factor: f32,
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
total_delay: Option<Duration>,
current_delay: Option<Duration>,
cumulative_delay: Duration,
attempts: usize,
}
impl Iterator for ExponentialBackoff {
type Item = Duration;
fn next(&mut self) -> Option<Self::Item> {
if self.attempts >= self.max_times.unwrap_or(usize::MAX) {
return None;
}
self.attempts += 1;
let mut tmp_cur = match self.current_delay {
None => {
// If current_delay is None, it's must be the first time to retry.
self.min_delay
}
Some(mut cur) => {
// If current delay larger than max delay, we should stop increment anymore.
if let Some(max_delay) = self.max_delay {
if cur < max_delay {
cur = saturating_mul(cur, self.factor);
}
if cur > max_delay {
cur = max_delay;
}
} else {
cur = saturating_mul(cur, self.factor);
}
cur
}
};
let current_delay = tmp_cur;
// If jitter is enabled, add random jitter based on min delay.
if self.jitter {
tmp_cur = tmp_cur.saturating_add(tmp_cur.mul_f32(self.rng.f32()));
}
// Check if adding the current delay would exceed the total delay limit.
let total_delay_check = self
.total_delay
.is_none_or(|total| self.cumulative_delay + tmp_cur <= total);
if !total_delay_check {
return None;
}
if self.total_delay.is_some() {
self.cumulative_delay = self.cumulative_delay.saturating_add(tmp_cur);
}
self.current_delay = Some(current_delay);
Some(tmp_cur)
}
}
#[inline]
pub(crate) fn saturating_mul(d: Duration, rhs: f32) -> Duration {
Duration::try_from_secs_f32(rhs * d.as_secs_f32()).unwrap_or(Duration::MAX)
}
#[cfg(test)]
mod tests {
use core::time::Duration;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
use crate::BackoffBuilder;
use crate::ExponentialBuilder;
const TEST_BUILDER: ExponentialBuilder = ExponentialBuilder::new()
.with_jitter()
.with_factor(1.5)
.with_min_delay(Duration::from_secs(2))
.with_max_delay(Duration::from_secs(30))
.with_max_times(5);
#[test]
fn test_exponential_default() {
let mut exp = ExponentialBuilder::default().build();
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(Some(Duration::from_secs(2)), exp.next());
assert_eq!(Some(Duration::from_secs(4)), exp.next());
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_factor() {
let mut exp = ExponentialBuilder::default().with_factor(1.5).build();
assert_eq!(Some(Duration::from_secs_f32(1.0)), exp.next());
assert_eq!(Some(Duration::from_secs_f32(1.5)), exp.next());
assert_eq!(Some(Duration::from_secs_f32(2.25)), exp.next());
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_jitter() {
let mut exp = ExponentialBuilder::default().with_jitter().build();
let v = exp.next().expect("value must valid");
assert!(v >= Duration::from_secs(1), "current: {v:?}");
assert!(v < Duration::from_secs(2), "current: {v:?}");
let v = exp.next().expect("value must valid");
assert!(v >= Duration::from_secs(2), "current: {v:?}");
assert!(v < Duration::from_secs(4), "current: {v:?}");
let v = exp.next().expect("value must valid");
assert!(v >= Duration::from_secs(4), "current: {v:?}");
assert!(v < Duration::from_secs(8), "current: {v:?}");
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_min_delay() {
let mut exp = ExponentialBuilder::default()
.with_min_delay(Duration::from_millis(500))
.build();
assert_eq!(Some(Duration::from_millis(500)), exp.next());
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(Some(Duration::from_secs(2)), exp.next());
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_total_delay() {
let mut exp = ExponentialBuilder::default()
.with_min_delay(Duration::from_secs(1))
.with_factor(1.0)
.with_total_delay(Some(Duration::from_secs(3)))
.with_max_times(5)
.build();
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_no_max_times_with_default() {
let mut exp = ExponentialBuilder::default()
.with_min_delay(Duration::from_secs(1))
.with_factor(1_f32)
.without_max_times()
.build();
// to fully test we would need to call this `usize::MAX`
// which seems unreasonable for a test as it would take too long...
for _ in 0..10_000 {
assert_eq!(Some(Duration::from_secs(1)), exp.next());
}
}
#[test]
fn test_exponential_max_delay_with_default() {
let mut exp = ExponentialBuilder::default()
.with_max_delay(Duration::from_secs(2))
.build();
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(Some(Duration::from_secs(2)), exp.next());
assert_eq!(Some(Duration::from_secs(2)), exp.next());
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_no_max_delay_with_default() {
let mut exp = ExponentialBuilder::default()
.with_min_delay(Duration::from_secs(1))
.with_factor(10_000_000_000_f32)
.without_max_delay()
.with_max_times(4)
.build();
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(Some(Duration::from_secs(10_000_000_000)), exp.next());
assert_eq!(Some(Duration::MAX), exp.next());
assert_eq!(Some(Duration::MAX), exp.next());
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_max_delay_without_default_1() {
let mut exp = ExponentialBuilder {
jitter: false,
seed: Some(0x2fdb0020ffc7722b),
factor: 10_000_000_000_f32,
min_delay: Duration::from_secs(1),
max_delay: None,
max_times: None,
total_delay: None,
}
.build();
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(Some(Duration::from_secs(10_000_000_000)), exp.next());
assert_eq!(Some(Duration::MAX), exp.next());
assert_eq!(Some(Duration::MAX), exp.next());
}
#[test]
fn test_exponential_max_delay_without_default_2() {
let mut exp = ExponentialBuilder {
jitter: true,
seed: Some(0x2fdb0020ffc7722b),
factor: 10_000_000_000_f32,
min_delay: Duration::from_secs(10_000_000_000),
max_delay: None,
max_times: Some(2),
total_delay: None,
}
.build();
let v = exp.next().expect("value must valid");
assert!(v >= Duration::from_secs(10_000_000_000), "current: {v:?}");
assert!(v < Duration::from_secs(20_000_000_000), "current: {v:?}");
assert_eq!(Some(Duration::MAX), exp.next());
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_max_delay_without_default_3() {
let mut exp = ExponentialBuilder {
jitter: false,
seed: Some(0x2fdb0020ffc7722b),
factor: 10_000_000_000_f32,
min_delay: Duration::from_secs(10_000_000_000),
max_delay: Some(Duration::from_secs(60_000_000_000)),
max_times: Some(3),
total_delay: None,
}
.build();
assert_eq!(Some(Duration::from_secs(10_000_000_000)), exp.next());
assert_eq!(Some(Duration::from_secs(60_000_000_000)), exp.next());
assert_eq!(Some(Duration::from_secs(60_000_000_000)), exp.next());
assert_eq!(None, exp.next());
}
#[test]
fn test_exponential_max_times() {
let mut exp = ExponentialBuilder::default().with_max_times(1).build();
assert_eq!(Some(Duration::from_secs(1)), exp.next());
assert_eq!(None, exp.next());
}
// allow assertions on constants because they are not optimized out by unit tests
#[allow(clippy::assertions_on_constants)]
#[test]
fn test_exponential_const_builder() {
assert!(TEST_BUILDER.jitter);
assert_eq!(TEST_BUILDER.factor, 1.5);
assert_eq!(TEST_BUILDER.min_delay, Duration::from_secs(2));
assert_eq!(TEST_BUILDER.max_delay, Some(Duration::from_secs(30)));
assert_eq!(TEST_BUILDER.max_times, Some(5));
}
}

345
vendor/backon/src/backoff/fibonacci.rs vendored Normal file
View File

@@ -0,0 +1,345 @@
use core::time::Duration;
use crate::backoff::BackoffBuilder;
/// FibonacciBuilder is used to build a [`FibonacciBackoff`] which offers a delay with Fibonacci-based retries.
///
/// # Default
///
/// - jitter: false
/// - min_delay: 1s
/// - max_delay: 60s
/// - max_times: 3
///
/// # Examples
///
/// ```no_run
/// use anyhow::Result;
/// use backon::FibonacciBuilder;
/// use backon::Retryable;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch.retry(FibonacciBuilder::default()).await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
#[derive(Debug, Clone, Copy)]
pub struct FibonacciBuilder {
jitter: bool,
seed: Option<u64>,
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
}
impl Default for FibonacciBuilder {
fn default() -> Self {
Self::new()
}
}
impl FibonacciBuilder {
/// Create a new `FibonacciBuilder` with default values.
pub const fn new() -> Self {
Self {
jitter: false,
seed: None,
min_delay: Duration::from_secs(1),
max_delay: Some(Duration::from_secs(60)),
max_times: Some(3),
}
}
/// Set the jitter for the backoff.
///
/// When jitter is enabled, FibonacciBackoff will add a random jitter between `(0, current_delay)` to the delay.
pub const fn with_jitter(mut self) -> Self {
self.jitter = true;
self
}
/// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
/// Set the minimum delay for the backoff.
pub const fn with_min_delay(mut self, min_delay: Duration) -> Self {
self.min_delay = min_delay;
self
}
/// Set the maximum delay for the current backoff.
///
/// The delay will not increase if the current delay exceeds the maximum delay.
pub const fn with_max_delay(mut self, max_delay: Duration) -> Self {
self.max_delay = Some(max_delay);
self
}
/// Set no maximum delay for the backoff.
///
/// The delay will keep increasing.
///
/// _The delay will saturate at `Duration::MAX` which is an **unrealistic** delay._
pub const fn without_max_delay(mut self) -> Self {
self.max_delay = None;
self
}
/// Set the maximum number of attempts for the current backoff.
///
/// The backoff will stop if the maximum number of attempts is reached.
pub const fn with_max_times(mut self, max_times: usize) -> Self {
self.max_times = Some(max_times);
self
}
/// Set no maximum number of attempts for the current backoff.
///
/// The backoff will not stop by itself.
///
/// _The backoff could stop reaching `usize::MAX` attempts but this is **unrealistic**._
pub const fn without_max_times(mut self) -> Self {
self.max_times = None;
self
}
}
impl BackoffBuilder for FibonacciBuilder {
type Backoff = FibonacciBackoff;
fn build(self) -> Self::Backoff {
FibonacciBackoff {
jitter: self.jitter,
rng: if let Some(seed) = self.seed {
fastrand::Rng::with_seed(seed)
} else {
#[cfg(feature = "std")]
let rng = fastrand::Rng::new();
#[cfg(not(feature = "std"))]
let rng = fastrand::Rng::with_seed(super::RANDOM_SEED);
rng
},
min_delay: self.min_delay,
max_delay: self.max_delay,
max_times: self.max_times,
previous_delay: None,
current_delay: None,
attempts: 0,
}
}
}
impl BackoffBuilder for &FibonacciBuilder {
type Backoff = FibonacciBackoff;
fn build(self) -> Self::Backoff {
(*self).build()
}
}
/// FibonacciBackoff offers a delay with Fibonacci-based retries.
///
/// This backoff strategy is constructed by [`FibonacciBuilder`].
#[doc(hidden)]
#[derive(Debug)]
pub struct FibonacciBackoff {
jitter: bool,
rng: fastrand::Rng,
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
previous_delay: Option<Duration>,
current_delay: Option<Duration>,
attempts: usize,
}
impl Iterator for FibonacciBackoff {
type Item = Duration;
fn next(&mut self) -> Option<Self::Item> {
if self.attempts >= self.max_times.unwrap_or(usize::MAX) {
return None;
}
self.attempts += 1;
match self.current_delay {
None => {
// If current_delay is None, it's must be the first time to retry.
let mut next = self.min_delay;
self.current_delay = Some(next);
// If jitter is enabled, add random jitter based on min delay.
if self.jitter {
next += next.mul_f32(self.rng.f32());
}
Some(next)
}
Some(cur) => {
let mut next = cur;
// If current delay larger than max delay, we should stop increment anymore.
if next < self.max_delay.unwrap_or(Duration::MAX) {
if let Some(prev) = self.previous_delay {
next = next.saturating_add(prev);
self.current_delay = Some(next);
}
self.previous_delay = Some(cur);
}
// If jitter is enabled, add random jitter based on min delay.
if self.jitter {
next += self.min_delay.mul_f32(self.rng.f32());
}
Some(next)
}
}
}
}
#[cfg(test)]
mod tests {
use core::time::Duration;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
use super::*;
const TEST_BUILDER: FibonacciBuilder = FibonacciBuilder::new()
.with_jitter()
.with_min_delay(Duration::from_secs(2))
.with_max_delay(Duration::from_secs(30))
.with_max_times(5);
#[test]
fn test_fibonacci_default() {
let mut fib = FibonacciBuilder::default().build();
assert_eq!(Some(Duration::from_secs(1)), fib.next());
assert_eq!(Some(Duration::from_secs(1)), fib.next());
assert_eq!(Some(Duration::from_secs(2)), fib.next());
assert_eq!(None, fib.next());
}
#[test]
fn test_fibonacci_jitter() {
let mut fib = FibonacciBuilder::default().with_jitter().build();
let v = fib.next().expect("value must valid");
assert!(v >= Duration::from_secs(1), "current: {v:?}");
assert!(v < Duration::from_secs(2), "current: {v:?}");
let v = fib.next().expect("value must valid");
assert!(v >= Duration::from_secs(1), "current: {v:?}");
assert!(v < Duration::from_secs(2), "current: {v:?}");
let v = fib.next().expect("value must valid");
assert!(v >= Duration::from_secs(2), "current: {v:?}");
assert!(v < Duration::from_secs(3), "current: {v:?}");
assert_eq!(None, fib.next());
}
#[test]
fn test_fibonacci_min_delay() {
let mut fib = FibonacciBuilder::default()
.with_min_delay(Duration::from_millis(500))
.build();
assert_eq!(Some(Duration::from_millis(500)), fib.next());
assert_eq!(Some(Duration::from_millis(500)), fib.next());
assert_eq!(Some(Duration::from_secs(1)), fib.next());
assert_eq!(None, fib.next());
}
#[test]
fn test_fibonacci_max_delay() {
let mut fib = FibonacciBuilder::default()
.with_max_times(4)
.with_max_delay(Duration::from_secs(2))
.build();
assert_eq!(Some(Duration::from_secs(1)), fib.next());
assert_eq!(Some(Duration::from_secs(1)), fib.next());
assert_eq!(Some(Duration::from_secs(2)), fib.next());
assert_eq!(Some(Duration::from_secs(2)), fib.next());
assert_eq!(None, fib.next());
}
#[test]
fn test_fibonacci_no_max_delay() {
let mut fib = FibonacciBuilder::default()
.with_max_times(4)
.with_min_delay(Duration::from_secs(10_000_000_000_000_000_000))
.without_max_delay()
.build();
assert_eq!(
Some(Duration::from_secs(10_000_000_000_000_000_000)),
fib.next()
);
assert_eq!(
Some(Duration::from_secs(10_000_000_000_000_000_000)),
fib.next()
);
assert_eq!(Some(Duration::MAX), fib.next());
assert_eq!(Some(Duration::MAX), fib.next());
assert_eq!(None, fib.next());
}
#[test]
fn test_fibonacci_max_times() {
let mut fib = FibonacciBuilder::default().with_max_times(6).build();
assert_eq!(Some(Duration::from_secs(1)), fib.next());
assert_eq!(Some(Duration::from_secs(1)), fib.next());
assert_eq!(Some(Duration::from_secs(2)), fib.next());
assert_eq!(Some(Duration::from_secs(3)), fib.next());
assert_eq!(Some(Duration::from_secs(5)), fib.next());
assert_eq!(Some(Duration::from_secs(8)), fib.next());
assert_eq!(None, fib.next());
}
#[test]
fn test_fibonacci_no_max_times() {
let mut fib = FibonacciBuilder::default()
.with_min_delay(Duration::from_secs(0))
.without_max_times()
.build();
// to fully test we would need to call this `usize::MAX`
// which seems unreasonable for a test as it would take too long...
for _ in 0..10_000 {
assert_eq!(Some(Duration::from_secs(0)), fib.next());
}
}
// allow assertions on constants because they are not optimized out by unit tests
#[allow(clippy::assertions_on_constants)]
#[test]
fn test_fibonacci_const_builder() {
assert!(TEST_BUILDER.jitter);
assert_eq!(TEST_BUILDER.min_delay, Duration::from_secs(2));
assert_eq!(TEST_BUILDER.max_delay, Some(Duration::from_secs(30)));
assert_eq!(TEST_BUILDER.max_times, Some(5));
}
}

18
vendor/backon/src/backoff/mod.rs vendored Normal file
View File

@@ -0,0 +1,18 @@
mod api;
pub use api::*;
mod constant;
pub use constant::ConstantBackoff;
pub use constant::ConstantBuilder;
mod fibonacci;
pub use fibonacci::FibonacciBackoff;
pub use fibonacci::FibonacciBuilder;
mod exponential;
pub use exponential::ExponentialBackoff;
pub use exponential::ExponentialBuilder;
// Random seed value for no_std (the value is "backon" in hex)
#[cfg(not(feature = "std"))]
const RANDOM_SEED: u64 = 0x6261636b6f6e;

365
vendor/backon/src/blocking_retry.rs vendored Normal file
View File

@@ -0,0 +1,365 @@
use core::time::Duration;
use crate::Backoff;
use crate::BlockingSleeper;
use crate::DefaultBlockingSleeper;
use crate::backoff::BackoffBuilder;
use crate::blocking_sleep::MaybeBlockingSleeper;
/// BlockingRetryable adds retry support for blocking functions.
///
/// For example:
///
/// - Functions without extra args:
///
/// ```ignore
/// fn fetch() -> Result<String> {
/// Ok("hello, world!".to_string())
/// }
/// ```
///
/// - Closures
///
/// ```ignore
/// || {
/// Ok("hello, world!".to_string())
/// }
/// ```
///
/// # Example
///
/// ```no_run
/// use anyhow::Result;
/// use backon::BlockingRetryable;
/// use backon::ExponentialBuilder;
///
/// fn fetch() -> Result<String> {
/// Ok("hello, world!".to_string())
/// }
///
/// fn main() -> Result<()> {
/// let content = fetch.retry(ExponentialBuilder::default()).call()?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub trait BlockingRetryable<B: BackoffBuilder, T, E, F: FnMut() -> Result<T, E>> {
/// Generate a new retry.
fn retry(self, builder: B) -> BlockingRetry<B::Backoff, T, E, F>;
}
impl<B, T, E, F> BlockingRetryable<B, T, E, F> for F
where
B: BackoffBuilder,
F: FnMut() -> Result<T, E>,
{
fn retry(self, builder: B) -> BlockingRetry<B::Backoff, T, E, F> {
BlockingRetry::new(self, builder.build())
}
}
/// Retry structure generated by [`BlockingRetryable`].
pub struct BlockingRetry<
B: Backoff,
T,
E,
F: FnMut() -> Result<T, E>,
SF: MaybeBlockingSleeper = DefaultBlockingSleeper,
RF = fn(&E) -> bool,
NF = fn(&E, Duration),
> {
backoff: B,
retryable: RF,
notify: NF,
f: F,
sleep_fn: SF,
}
impl<B, T, E, F> BlockingRetry<B, T, E, F>
where
B: Backoff,
F: FnMut() -> Result<T, E>,
{
/// Create a new retry.
fn new(f: F, backoff: B) -> Self {
BlockingRetry {
backoff,
retryable: |_: &E| true,
notify: |_: &E, _: Duration| {},
sleep_fn: DefaultBlockingSleeper::default(),
f,
}
}
}
impl<B, T, E, F, SF, RF, NF> BlockingRetry<B, T, E, F, SF, RF, NF>
where
B: Backoff,
F: FnMut() -> Result<T, E>,
SF: MaybeBlockingSleeper,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
/// Set the sleeper for retrying.
///
/// The sleeper should implement the [`BlockingSleeper`] trait. The simplest way is to use a closure like `Fn(Duration)`.
///
/// If not specified, we use the [`DefaultBlockingSleeper`].
///
/// # Examples
///
/// ```no_run
/// use anyhow::Result;
/// use backon::BlockingRetryable;
/// use backon::ExponentialBuilder;
///
/// fn fetch() -> Result<String> {
/// Ok("hello, world!".to_string())
/// }
///
/// fn main() -> Result<()> {
/// let retry = fetch
/// .retry(ExponentialBuilder::default())
/// .sleep(std::thread::sleep);
/// let content = retry.call()?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn sleep<SN: BlockingSleeper>(self, sleep_fn: SN) -> BlockingRetry<B, T, E, F, SN, RF, NF> {
BlockingRetry {
backoff: self.backoff,
retryable: self.retryable,
notify: self.notify,
f: self.f,
sleep_fn,
}
}
/// Set the conditions for retrying.
///
/// If not specified, all errors are considered retryable.
///
/// # Examples
///
/// ```no_run
/// use anyhow::Result;
/// use backon::BlockingRetryable;
/// use backon::ExponentialBuilder;
///
/// fn fetch() -> Result<String> {
/// Ok("hello, world!".to_string())
/// }
///
/// fn main() -> Result<()> {
/// let retry = fetch
/// .retry(ExponentialBuilder::default())
/// .when(|e| e.to_string() == "EOF");
/// let content = retry.call()?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn when<RN: FnMut(&E) -> bool>(
self,
retryable: RN,
) -> BlockingRetry<B, T, E, F, SF, RN, NF> {
BlockingRetry {
backoff: self.backoff,
retryable,
notify: self.notify,
f: self.f,
sleep_fn: self.sleep_fn,
}
}
/// Set to notify for all retry attempts.
///
/// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing.
///
/// If not specified, this operation does nothing.
///
/// # Examples
///
/// ```no_run
/// use core::time::Duration;
///
/// use anyhow::Result;
/// use backon::BlockingRetryable;
/// use backon::ExponentialBuilder;
///
/// fn fetch() -> Result<String> {
/// Ok("hello, world!".to_string())
/// }
///
/// fn main() -> Result<()> {
/// let retry = fetch.retry(ExponentialBuilder::default()).notify(
/// |err: &anyhow::Error, dur: Duration| {
/// println!("retrying error {:?} with sleeping {:?}", err, dur);
/// },
/// );
/// let content = retry.call()?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn notify<NN: FnMut(&E, Duration)>(
self,
notify: NN,
) -> BlockingRetry<B, T, E, F, SF, RF, NN> {
BlockingRetry {
backoff: self.backoff,
retryable: self.retryable,
notify,
f: self.f,
sleep_fn: self.sleep_fn,
}
}
}
impl<B, T, E, F, SF, RF, NF> BlockingRetry<B, T, E, F, SF, RF, NF>
where
B: Backoff,
F: FnMut() -> Result<T, E>,
SF: BlockingSleeper,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
/// Call the retried function.
///
/// TODO: implement [`FnOnce`] after it stable.
pub fn call(mut self) -> Result<T, E> {
loop {
let result = (self.f)();
match result {
Ok(v) => return Ok(v),
Err(err) => {
if !(self.retryable)(&err) {
return Err(err);
}
match self.backoff.next() {
None => return Err(err),
Some(dur) => {
(self.notify)(&err, dur);
self.sleep_fn.sleep(dur);
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use core::time::Duration;
use spin::Mutex;
use super::*;
use crate::ExponentialBuilder;
fn always_error() -> anyhow::Result<()> {
Err(anyhow::anyhow!("test_query meets error"))
}
#[test]
fn test_retry() -> anyhow::Result<()> {
let result = always_error
.retry(ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)))
.call();
assert!(result.is_err());
assert_eq!("test_query meets error", result.unwrap_err().to_string());
Ok(())
}
#[test]
fn test_retry_with_not_retryable_error() -> anyhow::Result<()> {
let error_times = Mutex::new(0);
let f = || {
let mut x = error_times.lock();
*x += 1;
Err::<(), anyhow::Error>(anyhow::anyhow!("not retryable"))
};
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(backoff)
// Only retry If error message is `retryable`
.when(|e| e.to_string() == "retryable")
.call();
assert!(result.is_err());
assert_eq!("not retryable", result.unwrap_err().to_string());
// `f` always returns error "not retryable", so it should be executed
// only once.
assert_eq!(*error_times.lock(), 1);
Ok(())
}
#[test]
fn test_retry_with_retryable_error() -> anyhow::Result<()> {
let error_times = Mutex::new(0);
let f = || {
// println!("I have been called!");
let mut x = error_times.lock();
*x += 1;
Err::<(), anyhow::Error>(anyhow::anyhow!("retryable"))
};
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(backoff)
// Only retry If error message is `retryable`
.when(|e| e.to_string() == "retryable")
.call();
assert!(result.is_err());
assert_eq!("retryable", result.unwrap_err().to_string());
// `f` always returns error "retryable", so it should be executed
// 4 times (retry 3 times).
assert_eq!(*error_times.lock(), 4);
Ok(())
}
#[test]
fn test_fn_mut_when_and_notify() -> anyhow::Result<()> {
let mut calls_retryable: Vec<()> = vec![];
let mut calls_notify: Vec<()> = vec![];
let f = || Err::<(), anyhow::Error>(anyhow::anyhow!("retryable"));
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(backoff)
.when(|_| {
calls_retryable.push(());
true
})
.notify(|_, _| {
calls_notify.push(());
})
.call();
assert!(result.is_err());
assert_eq!("retryable", result.unwrap_err().to_string());
// `f` always returns error "retryable", so it should be executed
// 4 times (retry 3 times).
assert_eq!(calls_retryable.len(), 4);
assert_eq!(calls_notify.len(), 3);
Ok(())
}
}

View File

@@ -0,0 +1,237 @@
use core::time::Duration;
use crate::Backoff;
use crate::BlockingSleeper;
use crate::DefaultBlockingSleeper;
use crate::backoff::BackoffBuilder;
use crate::blocking_sleep::MaybeBlockingSleeper;
/// BlockingRetryableWithContext adds retry support for blocking functions.
pub trait BlockingRetryableWithContext<
B: BackoffBuilder,
T,
E,
Ctx,
F: FnMut(Ctx) -> (Ctx, Result<T, E>),
>
{
/// Generate a new retry
fn retry(self, builder: B) -> BlockingRetryWithContext<B::Backoff, T, E, Ctx, F>;
}
impl<B, T, E, Ctx, F> BlockingRetryableWithContext<B, T, E, Ctx, F> for F
where
B: BackoffBuilder,
F: FnMut(Ctx) -> (Ctx, Result<T, E>),
{
fn retry(self, builder: B) -> BlockingRetryWithContext<B::Backoff, T, E, Ctx, F> {
BlockingRetryWithContext::new(self, builder.build())
}
}
/// Retry structure generated by [`BlockingRetryableWithContext`].
pub struct BlockingRetryWithContext<
B: Backoff,
T,
E,
Ctx,
F: FnMut(Ctx) -> (Ctx, Result<T, E>),
SF: MaybeBlockingSleeper = DefaultBlockingSleeper,
RF = fn(&E) -> bool,
NF = fn(&E, Duration),
> {
backoff: B,
retryable: RF,
notify: NF,
f: F,
sleep_fn: SF,
ctx: Option<Ctx>,
}
impl<B, T, E, Ctx, F> BlockingRetryWithContext<B, T, E, Ctx, F>
where
B: Backoff,
F: FnMut(Ctx) -> (Ctx, Result<T, E>),
{
/// Create a new retry.
fn new(f: F, backoff: B) -> Self {
BlockingRetryWithContext {
backoff,
retryable: |_: &E| true,
notify: |_: &E, _: Duration| {},
sleep_fn: DefaultBlockingSleeper::default(),
f,
ctx: None,
}
}
}
impl<B, T, E, Ctx, F, SF, RF, NF> BlockingRetryWithContext<B, T, E, Ctx, F, SF, RF, NF>
where
B: Backoff,
F: FnMut(Ctx) -> (Ctx, Result<T, E>),
SF: MaybeBlockingSleeper,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
/// Set the context for retrying.
///
/// Context is used to capture ownership manually to prevent lifetime issues.
pub fn context(self, context: Ctx) -> BlockingRetryWithContext<B, T, E, Ctx, F, SF, RF, NF> {
BlockingRetryWithContext {
backoff: self.backoff,
retryable: self.retryable,
notify: self.notify,
f: self.f,
sleep_fn: self.sleep_fn,
ctx: Some(context),
}
}
/// Set the sleeper for retrying.
///
/// The sleeper should implement the [`BlockingSleeper`] trait. The simplest way is to use a closure like `Fn(Duration)`.
///
/// If not specified, we use the [`DefaultBlockingSleeper`].
pub fn sleep<SN: BlockingSleeper>(
self,
sleep_fn: SN,
) -> BlockingRetryWithContext<B, T, E, Ctx, F, SN, RF, NF> {
BlockingRetryWithContext {
backoff: self.backoff,
retryable: self.retryable,
notify: self.notify,
f: self.f,
sleep_fn,
ctx: self.ctx,
}
}
/// Set the conditions for retrying.
///
/// If not specified, all errors are considered retryable.
pub fn when<RN: FnMut(&E) -> bool>(
self,
retryable: RN,
) -> BlockingRetryWithContext<B, T, E, Ctx, F, SF, RN, NF> {
BlockingRetryWithContext {
backoff: self.backoff,
retryable,
notify: self.notify,
f: self.f,
sleep_fn: self.sleep_fn,
ctx: self.ctx,
}
}
/// Set to notify for all retry attempts.
///
/// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing.
///
/// If not specified, this operation does nothing.
pub fn notify<NN: FnMut(&E, Duration)>(
self,
notify: NN,
) -> BlockingRetryWithContext<B, T, E, Ctx, F, SF, RF, NN> {
BlockingRetryWithContext {
backoff: self.backoff,
retryable: self.retryable,
notify,
f: self.f,
sleep_fn: self.sleep_fn,
ctx: self.ctx,
}
}
}
impl<B, T, E, Ctx, F, SF, RF, NF> BlockingRetryWithContext<B, T, E, Ctx, F, SF, RF, NF>
where
B: Backoff,
F: FnMut(Ctx) -> (Ctx, Result<T, E>),
SF: BlockingSleeper,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
/// Call the retried function.
///
/// TODO: implement [`FnOnce`] after it stable.
pub fn call(mut self) -> (Ctx, Result<T, E>) {
let mut ctx = self.ctx.take().expect("context must be valid");
loop {
let (xctx, result) = (self.f)(ctx);
// return ctx ownership back
ctx = xctx;
match result {
Ok(v) => return (ctx, Ok(v)),
Err(err) => {
if !(self.retryable)(&err) {
return (ctx, Err(err));
}
match self.backoff.next() {
None => return (ctx, Err(err)),
Some(dur) => {
(self.notify)(&err, dur);
self.sleep_fn.sleep(dur);
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::string::ToString;
use core::time::Duration;
use anyhow::Result;
use anyhow::anyhow;
use spin::Mutex;
use super::*;
use crate::ExponentialBuilder;
struct Test;
impl Test {
fn hello(&mut self) -> Result<usize> {
Err(anyhow!("not retryable"))
}
}
#[test]
fn test_retry_with_not_retryable_error() -> Result<()> {
let error_times = Mutex::new(0);
let test = Test;
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let (_, result) = {
|mut v: Test| {
let mut x = error_times.lock();
*x += 1;
let res = v.hello();
(v, res)
}
}
.retry(backoff)
.context(test)
// Only retry If error message is `retryable`
.when(|e| e.to_string() == "retryable")
.call();
assert!(result.is_err());
assert_eq!("not retryable", result.unwrap_err().to_string());
// `f` always returns error "not retryable", so it should be executed
// only once.
assert_eq!(*error_times.lock(), 1);
Ok(())
}
}

56
vendor/backon/src/blocking_sleep.rs vendored Normal file
View File

@@ -0,0 +1,56 @@
use core::time::Duration;
/// A sleeper is used sleep for a specified duration.
pub trait BlockingSleeper: 'static {
/// sleep for a specified duration.
fn sleep(&self, dur: Duration);
}
/// A stub trait allowing non-[`BlockingSleeper`] types to be used as a generic parameter in [`BlockingRetry`][crate::BlockingRetry].
/// It does not provide actual functionality.
#[doc(hidden)]
pub trait MaybeBlockingSleeper: 'static {}
/// All `BlockingSleeper` will implement `MaybeBlockingSleeper`, but not vice versa.
impl<T: BlockingSleeper + ?Sized> MaybeBlockingSleeper for T {}
/// All `Fn(Duration)` implements `Sleeper`.
impl<F: Fn(Duration) + 'static> BlockingSleeper for F {
fn sleep(&self, dur: Duration) {
self(dur)
}
}
/// The default implementation of `Sleeper` when no features are enabled.
///
/// It will fail to compile if a containing [`Retry`][crate::Retry] is `.await`ed without calling [`Retry::sleep`][crate::Retry::sleep] to provide a valid sleeper.
#[cfg(not(feature = "std-blocking-sleep"))]
pub type DefaultBlockingSleeper = PleaseEnableAFeatureOrProvideACustomSleeper;
/// The default implementation of `Sleeper` while feature `std-blocking-sleep` enabled.
///
/// it uses [`std::thread::sleep`].
#[cfg(feature = "std-blocking-sleep")]
pub type DefaultBlockingSleeper = StdSleeper;
/// A placeholder type that does not implement [`Sleeper`] and will therefore fail to compile if used as one.
///
/// Users should enable a feature of this crate that provides a valid [`Sleeper`] implementation when this type appears in compilation errors. Alternatively, a custom [`Sleeper`] implementation should be provided where necessary, such as in [`crate::Retry::sleeper`].
#[doc(hidden)]
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Default)]
pub struct PleaseEnableAFeatureOrProvideACustomSleeper;
/// Implement `MaybeSleeper` but not `Sleeper`.
impl MaybeBlockingSleeper for PleaseEnableAFeatureOrProvideACustomSleeper {}
/// The implementation of `StdSleeper` uses [`std::thread::sleep`].
#[cfg(feature = "std-blocking-sleep")]
#[derive(Clone, Copy, Debug, Default)]
pub struct StdSleeper;
#[cfg(feature = "std-blocking-sleep")]
impl BlockingSleeper for StdSleeper {
fn sleep(&self, dur: Duration) {
std::thread::sleep(dur)
}
}

View File

@@ -0,0 +1,19 @@
Retry an async function.
```rust
use backon::ExponentialBuilder;
use backon::Retryable;
use anyhow::Result;
async fn fetch() -> Result<String> {
Ok("Hello, World!".to_string())
}
#[tokio::main]
async fn main() -> Result<()> {
let content = fetch.retry(ExponentialBuilder::default()).await?;
println!("fetch succeeded: {}", content);
Ok(())
}
```

View File

@@ -0,0 +1,17 @@
Retry an closure.
```rust
use backon::ExponentialBuilder;
use backon::Retryable;
use backon::BlockingRetryable;
fn main() -> anyhow::Result<()> {
let var = 42;
// `f` can use input variables
let f = || Ok::<u32, anyhow::Error>(var);
let result = f.retry(backon::ExponentialBuilder::default()).call()?;
println!("var = {result}");
Ok(())
}
```

View File

@@ -0,0 +1,43 @@
Let's implement a custom async Sleeper, say you are using Monoio as your async
runtime, you may want to implement it with `monoio::time::sleep()`. If you want
to implement a custom blocking Sleeper, you will find it pretty similar.
```rust
use std::time::Duration;
use backon::Sleeper;
/// Sleeper implemented using `monoio::time::sleep()`.
struct MonoioSleeper;
impl Sleeper for MonoioSleeper {
type Sleep = monoio::time::Sleep;
fn sleep(&self, dur: Duration) -> Self::Sleep {
monoio::time::sleep(dur)
}
}
```
Then you can use it like:
```rust
use backon::ExponentialBuilder;
use backon::Retryable;
use anyhow::Result;
async fn fetch() -> Result<String> {
Ok("Hello, World!".to_string())
}
#[monoio::main(timer_enabled = true)]
async fn main() -> Result<()> {
let content = fetch
.retry(ExponentialBuilder::default())
.sleep(MonoioSleeper)
.await?;
println!("fetch succeeded: {}", content);
Ok(())
}
```

View File

@@ -0,0 +1,23 @@
Retry an async function inside `&mut self` functions.
```rust
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;
struct Test;
impl Test {
async fn fetch(&self, url: &str) -> Result<String> {
Ok(reqwest::get(url).await?.text().await?)
}
async fn run(&mut self) -> Result<String> {
let content = (|| async { self.fetch("https://www.rust-lang.org").await })
.retry(ExponentialBuilder::default())
.when(|e| e.to_string() == "retryable")
.await?;
Ok(content)
}
}
```

28
vendor/backon/src/docs/examples/mod.rs vendored Normal file
View File

@@ -0,0 +1,28 @@
//! Examples of using backon.
#[doc = include_str!("basic.md")]
pub mod basic {}
#[doc = include_str!("closure.md")]
pub mod closure {}
#[doc = include_str!("inside_mut_self.md")]
pub mod inside_mut_self {}
#[doc = include_str!("sqlx.md")]
pub mod sqlx {}
#[doc = include_str!("with_args.md")]
pub mod with_args {}
#[doc = include_str!("with_mut_self.md")]
pub mod with_mut_self {}
#[doc = include_str!("with_self.md")]
pub mod with_self {}
#[doc = include_str!("with_specific_error.md")]
pub mod with_specific_error {}
#[doc = include_str!("retry_after.md")]
pub mod retry_after {}

View File

@@ -0,0 +1,63 @@
Retry an async function with the `Retry-After` headers.
```no_run
use core::time::Duration;
use std::error::Error;
use std::fmt::Display;
use std::fmt::Formatter;
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;
use reqwest::header::HeaderMap;
use reqwest::StatusCode;
#[derive(Debug)]
struct HttpError {
headers: HeaderMap,
}
impl Display for HttpError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "http error")
}
}
impl Error for HttpError {}
async fn fetch() -> Result<String> {
let resp = reqwest::get("https://www.rust-lang.org").await?;
if resp.status() != StatusCode::OK {
let source = HttpError {
headers: resp.headers().clone(),
};
return Err(anyhow::Error::new(source));
}
Ok(resp.text().await?)
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let content = fetch
.retry(ExponentialBuilder::default())
.adjust(|err, dur| {
match err.downcast_ref::<HttpError>() {
Some(v) => {
if let Some(retry_after) = v.headers.get("Retry-After") {
// Parse the Retry-After header and adjust the backoff duration
let retry_after = retry_after.to_str().unwrap_or("0");
let retry_after = retry_after.parse::<u64>().unwrap_or(0);
Some(Duration::from_secs(retry_after))
} else {
dur
}
}
None => dur,
}
})
.await?;
println!("fetch succeeded: {}", content);
Ok(())
}
```

23
vendor/backon/src/docs/examples/sqlx.md vendored Normal file
View File

@@ -0,0 +1,23 @@
Retry sqlx operations.
```rust
use backon::Retryable;
use anyhow::Result;
use backon::ExponentialBuilder;
#[tokio::main]
async fn main() -> Result<()> {
let pool = sqlx::sqlite::SqlitePoolOptions::new()
.max_connections(5)
.connect("sqlite::memory:")
.await?;
let row: (i64,) = (|| sqlx::query_as("SELECT $1").bind(150_i64).fetch_one(&pool))
.retry(ExponentialBuilder::default())
.await?;
assert_eq!(row.0, 150);
Ok(())
}
```

View File

@@ -0,0 +1,23 @@
Retry an async function in a wasm32 environment using `backon` for exponential backoff, and `wasm-bindgen` + `spawn_local` to run async code in the browser.
```rust
use anyhow::Result;
use backon::{ExponentialBuilder, Retryable};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
async fn fetch() -> Result<String> {
Ok("Hello, wasm32!".to_string())
}
#[wasm_bindgen(start)]
fn start() {
spawn_local(async {
match fetch.retry(ExponentialBuilder::default()).await {
Ok(content) => web_sys::console::log_1(&format!("fetch succeeded: {}", content).into()),
Err(e) => web_sys::console::error_1(&format!("fetch failed: {:?}", e).into()),
}
});
}
```

View File

@@ -0,0 +1,24 @@
Retry function with args.
It's a pity that rust doesn't allow us to implement `Retryable` for async function with args. So we have to use a workaround to make it work.
```rust
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;
async fn fetch(url: &str) -> Result<String> {
Ok(reqwest::get(url).await?.text().await?)
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let content = (|| async { fetch("https://www.rust-lang.org").await })
.retry(ExponentialBuilder::default())
.when(|e| e.to_string() == "retryable")
.await?;
println!("fetch succeeded: {}", content);
Ok(())
}
```

View File

@@ -0,0 +1,36 @@
Retry an async function which takes `&mut self` as receiver.
This is a bit more complex since we need to capture the receiver in the closure with ownership. backon supports this use case by `RetryableWithContext`.
```rust
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::RetryableWithContext;
struct Test;
impl Test {
async fn fetch(&mut self, url: &str) -> Result<String> {
Ok(reqwest::get(url).await?.text().await?)
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let test = Test;
let (_, result) = (|mut v: Test| async {
let res = v.fetch("https://www.rust-lang.org").await;
// Return input context back.
(v, res)
})
.retry(ExponentialBuilder::default())
// Passing context in.
.context(test)
.when(|e| e.to_string() == "retryable")
.await;
println!("fetch succeeded: {}", result.unwrap());
Ok(())
}
```

View File

@@ -0,0 +1,28 @@
Retry an async function which takes `&self` as receiver.
```rust
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;
struct Test;
impl Test {
async fn fetch(&self, url: &str) -> Result<String> {
Ok(reqwest::get(url).await?.text().await?)
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let test = Test;
let content = (|| async { test.fetch("https://www.rust-lang.org").await })
.retry(ExponentialBuilder::default())
.when(|e| e.to_string() == "retryable")
.await?;
println!("fetch succeeded: {}", content);
Ok(())
}
```

View File

@@ -0,0 +1,21 @@
Retry with specify retryable error by `when`.
```rust
use anyhow::Result;
use backon::ExponentialBuilder;
use backon::Retryable;
async fn fetch() -> Result<String> {
Ok("Hello, World!".to_string())
}
#[tokio::main]
async fn main() -> Result<()> {
let content = fetch
.retry(ExponentialBuilder::default())
.when(|e| e.to_string() == "retryable")
.await?;
println!("fetch succeeded: {}", content);
Ok(())
}
```

3
vendor/backon/src/docs/mod.rs vendored Normal file
View File

@@ -0,0 +1,3 @@
//! Docs for the backon crate, like [`examples`].
pub mod examples;

View File

@@ -0,0 +1,22 @@
use core::time::Duration;
use crate::BlockingSleeper;
use crate::Sleeper;
/// A no_std async sleeper based on the embassy framework (https://embassy.dev)
#[derive(Clone, Copy, Debug, Default)]
pub struct EmbassySleeper;
impl Sleeper for EmbassySleeper {
type Sleep = embassy_time::Timer;
fn sleep(&self, dur: Duration) -> Self::Sleep {
embassy_time::Timer::after_millis(dur.as_millis() as u64)
}
}
impl BlockingSleeper for EmbassySleeper {
fn sleep(&self, dur: Duration) {
embassy_time::block_for(embassy_time::Duration::from_millis(dur.as_millis() as u64));
}
}

258
vendor/backon/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,258 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/Xuanwo/backon/main/.github/assets/logo.jpeg"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
//! [![Build Status]][actions] [![Latest Version]][crates.io] [![](https://img.shields.io/discord/1111711408875393035?logo=discord&label=discord)](https://discord.gg/8ARnvtJePD)
//!
//! [Build Status]: https://img.shields.io/github/actions/workflow/status/Xuanwo/backon/ci.yml?branch=main
//! [actions]: https://github.com/Xuanwo/backon/actions?query=branch%3Amain
//! [Latest Version]: https://img.shields.io/crates/v/backon.svg
//! [crates.io]: https://crates.io/crates/backon
//!
//! <img src="https://raw.githubusercontent.com/Xuanwo/backon/main/.github/assets/logo.jpeg" alt="BackON" width="38.2%"/>
//!
//! Make **retry** like a built-in feature provided by Rust.
//!
//! - **Simple**: Just like a built-in feature: `your_fn.retry(ExponentialBuilder::default()).await`.
//! - **Flexible**: Supports both blocking and async functions.
//! - **Powerful**: Allows control over retry behavior such as [`when`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.when) and [`notify`](https://docs.rs/backon/latest/backon/struct.Retry.html#method.notify).
//! - **Customizable**: Supports custom retry strategies like [exponential](https://docs.rs/backon/latest/backon/struct.ExponentialBuilder.html), [constant](https://docs.rs/backon/latest/backon/struct.ConstantBuilder.html), etc.
//!
//! # Backoff
//!
//! Retry in BackON requires a backoff strategy. BackON will accept a [`BackoffBuilder`] which will generate a new [`Backoff`] for each retry. It also accepts any object that implements [`Backoff`]. You can therefore easily implement your own custom backoff strategy.
//!
//! BackON provides several backoff implementations with reasonable defaults:
//!
//! - [`ConstantBuilder`]: backoff with a constant delay, limited to a specific number of attempts.
//! - [`ExponentialBuilder`]: backoff with an exponential delay, also supports jitter.
//! - [`FibonacciBuilder`]: backoff with a fibonacci delay, also supports jitter.
//!
//! # Sleep
//!
//! Retry in BackON requires an implementation for sleeping, such an implementation
//! is called a Sleeper, it will implement [`Sleeper`] or [`BlockingSleeper`] depending
//! on if it is going to be used in an asynchronous context.
//!
//! ## Default Sleeper
//!
//! Currently, BackON has 3 built-in Sleeper implementations for different
//! environments, they are gated under their own features, which are enabled
//! by default:
//!
//! | `Sleeper` | feature | Environment | Asynchronous |
//! |-------------------------|---------------------|-------------|---------------|
//! | [`TokioSleeper`] | tokio-sleep | non-wasm32 | Yes |
//! | [`GlooTimersSleep`] | gloo-timers-sleep | wasm32 | Yes |
//! | [`FuturesTimerSleeper`] | futures-timer-sleep |wasm/non-wasm| Yes |
//! | [`EmbassySleep`] | embassy-sleep | no_std | Yes |
//! | [`StdSleeper`] | std-blocking-sleep | std | No |
//!
//! ## Custom Sleeper
//!
//! If you do not want to use the built-in Sleeper, you CAN provide a custom
//! implementation, let's implement an asynchronous dummy Sleeper that does
//! not sleep at all. You will find it pretty similar when you implement a
//! blocking one.
//!
//! ```
//! use std::time::Duration;
//!
//! use backon::Sleeper;
//!
//! /// A dummy `Sleeper` impl that prints then becomes ready!
//! struct DummySleeper;
//!
//! impl Sleeper for DummySleeper {
//! type Sleep = std::future::Ready<()>;
//!
//! fn sleep(&self, dur: Duration) -> Self::Sleep {
//! println!("Hello from DummySleeper!");
//! std::future::ready(())
//! }
//! }
//! ```
//!
//! ## The empty Sleeper
//!
//! If neither feature is enabled nor a custom implementation is provided, BackON
//! will fallback to the empty sleeper, in which case, a compile-time error that
//! `PleaseEnableAFeatureOrProvideACustomSleeper needs to implement Sleeper or
//! BlockingSleeper` will be raised to remind you to choose or bring a real Sleeper
//! implementation.
//!
//! # Retry
//!
//! For additional examples, please visit [`docs::examples`].
//!
//! ## Retry an async function
//!
//! ```rust
//! use anyhow::Result;
//! use backon::ExponentialBuilder;
//! use backon::Retryable;
//! use core::time::Duration;
//!
//! async fn fetch() -> Result<String> {
//! Ok("hello, world!".to_string())
//! }
//!
//! #[tokio::main(flavor = "current_thread")]
//! async fn main() -> Result<()> {
//! let content = fetch
//! // Retry with exponential backoff
//! .retry(ExponentialBuilder::default())
//! // Sleep implementation, default to tokio::time::sleep if `tokio-sleep` has been enabled.
//! .sleep(tokio::time::sleep)
//! // When to retry
//! .when(|e| e.to_string() == "EOF")
//! // Notify when retrying
//! .notify(|err: &anyhow::Error, dur: Duration| {
//! println!("retrying {:?} after {:?}", err, dur);
//! })
//! .await?;
//! println!("fetch succeeded: {}", content);
//!
//! Ok(())
//! }
//! ```
//!
//! ## Retry a blocking function
//!
//! ```rust
//! use anyhow::Result;
//! use backon::BlockingRetryable;
//! use backon::ExponentialBuilder;
//! use core::time::Duration;
//!
//! fn fetch() -> Result<String> {
//! Ok("hello, world!".to_string())
//! }
//!
//! fn main() -> Result<()> {
//! let content = fetch
//! // Retry with exponential backoff
//! .retry(ExponentialBuilder::default())
//! // Sleep implementation, default to std::thread::sleep if `std-blocking-sleep` has been enabled.
//! .sleep(std::thread::sleep)
//! // When to retry
//! .when(|e| e.to_string() == "EOF")
//! // Notify when retrying
//! .notify(|err: &anyhow::Error, dur: Duration| {
//! println!("retrying {:?} after {:?}", err, dur);
//! })
//! .call()?;
//! println!("fetch succeeded: {}", content);
//!
//! Ok(())
//! }
//! ```
//!
//! ## Retry an async function with context
//!
//! Sometimes users can meet the problem that the async function is needs to take `FnMut`:
//!
//! ```shell
//! error: captured variable cannot escape `FnMut` closure body
//! --> src/retry.rs:404:27
//! |
//! 400 | let mut test = Test;
//! | -------- variable defined here
//! ...
//! 404 | let result = { || async { test.hello().await } }
//! | - ^^^^^^^^----^^^^^^^^^^^^^^^^
//! | | | |
//! | | | variable captured here
//! | | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
//! | inferred to be a `FnMut` closure
//! |
//! = note: `FnMut` closures only have access to their captured variables while they are executing...
//! = note: ...therefore, they cannot allow references to captured variables to escape
//! ```
//!
//! `RetryableWithContext` is designed for this, it allows you to pass a context
//! to the retry function, and return it back after the retry is done.
//!
//! ```no_run
//! use anyhow::anyhow;
//! use anyhow::Result;
//! use backon::ExponentialBuilder;
//! use backon::RetryableWithContext;
//!
//! struct Test;
//!
//! impl Test {
//! async fn hello(&mut self) -> Result<usize> {
//! Err(anyhow!("not retryable"))
//! }
//! }
//!
//! #[tokio::main(flavor = "current_thread")]
//! async fn main() -> Result<()> {
//! let mut test = Test;
//!
//! // (Test, Result<usize>)
//! let (_, result) = {
//! |mut v: Test| async {
//! let res = v.hello().await;
//! (v, res)
//! }
//! }
//! .retry(ExponentialBuilder::default())
//! .context(test)
//! .await;
//!
//! Ok(())
//! }
//! ```
#![deny(missing_docs)]
#![deny(unused_qualifications)]
#![no_std]
#[cfg(feature = "std-blocking-sleep")]
extern crate std;
mod backoff;
pub use backoff::*;
mod retry;
pub use retry::Retry;
pub use retry::Retryable;
mod retry_with_context;
pub use retry_with_context::RetryWithContext;
pub use retry_with_context::RetryableWithContext;
mod sleep;
pub use sleep::DefaultSleeper;
#[cfg(feature = "futures-timer-sleep")]
pub use sleep::FuturesTimerSleeper;
#[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))]
pub use sleep::GlooTimersSleep;
pub use sleep::Sleeper;
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))]
pub use sleep::TokioSleeper;
mod blocking_retry;
pub use blocking_retry::BlockingRetry;
pub use blocking_retry::BlockingRetryable;
mod blocking_retry_with_context;
pub use blocking_retry_with_context::BlockingRetryWithContext;
pub use blocking_retry_with_context::BlockingRetryableWithContext;
mod blocking_sleep;
pub use blocking_sleep::BlockingSleeper;
pub use blocking_sleep::DefaultBlockingSleeper;
#[cfg(feature = "std-blocking-sleep")]
pub use blocking_sleep::StdSleeper;
#[cfg(feature = "embassy-sleep")]
mod embassy_timer_sleep;
#[cfg(feature = "embassy-sleep")]
pub use embassy_timer_sleep::EmbassySleeper;
#[cfg(docsrs)]
pub mod docs;

587
vendor/backon/src/retry.rs vendored Normal file
View File

@@ -0,0 +1,587 @@
use core::future::Future;
use core::pin::Pin;
use core::task::Context;
use core::task::Poll;
use core::task::ready;
use core::time::Duration;
use crate::Backoff;
use crate::DefaultSleeper;
use crate::Sleeper;
use crate::backoff::BackoffBuilder;
use crate::sleep::MaybeSleeper;
/// Retryable will add retry support for functions that produce futures with results.
///
/// This means all types that implement `FnMut() -> impl Future<Output = Result<T, E>>`
/// will be able to use `retry`.
///
/// For example:
///
/// - Functions without extra args:
///
/// ```ignore
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org").await?.text().await?)
/// }
/// ```
///
/// - Closures
///
/// ```ignore
/// || async {
/// let x = reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?;
///
/// Err(anyhow::anyhow!(x))
/// }
/// ```
pub trait Retryable<
B: BackoffBuilder,
T,
E,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
>
{
/// Generate a new retry
fn retry(self, builder: B) -> Retry<B::Backoff, T, E, Fut, FutureFn>;
}
impl<B, T, E, Fut, FutureFn> Retryable<B, T, E, Fut, FutureFn> for FutureFn
where
B: BackoffBuilder,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
{
fn retry(self, builder: B) -> Retry<B::Backoff, T, E, Fut, FutureFn> {
Retry::new(self, builder.build())
}
}
/// Struct generated by [`Retryable`].
pub struct Retry<
B: Backoff,
T,
E,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
SF: MaybeSleeper = DefaultSleeper,
RF = fn(&E) -> bool,
NF = fn(&E, Duration),
AF = fn(&E, Option<Duration>) -> Option<Duration>,
> {
backoff: B,
future_fn: FutureFn,
retryable_fn: RF,
notify_fn: NF,
sleep_fn: SF,
adjust_fn: AF,
state: State<T, E, Fut, SF::Sleep>,
}
impl<B, T, E, Fut, FutureFn> Retry<B, T, E, Fut, FutureFn>
where
B: Backoff,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
{
/// Initiate a new retry.
fn new(future_fn: FutureFn, backoff: B) -> Self {
Retry {
backoff,
future_fn,
retryable_fn: |_: &E| true,
notify_fn: |_: &E, _: Duration| {},
adjust_fn: |_: &E, dur: Option<Duration>| dur,
sleep_fn: DefaultSleeper::default(),
state: State::Idle,
}
}
}
impl<B, T, E, Fut, FutureFn, SF, RF, NF, AF> Retry<B, T, E, Fut, FutureFn, SF, RF, NF, AF>
where
B: Backoff,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
SF: MaybeSleeper,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
AF: FnMut(&E, Option<Duration>) -> Option<Duration>,
{
/// Set the sleeper for retrying.
///
/// The sleeper should implement the [`Sleeper`] trait. The simplest way is to use a closure that returns a `Future`.
///
/// If not specified, we use the [`DefaultSleeper`].
///
/// ```no_run
/// use std::future::ready;
///
/// use anyhow::Result;
/// use backon::ExponentialBuilder;
/// use backon::Retryable;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch
/// .retry(ExponentialBuilder::default())
/// .sleep(|_| ready(()))
/// .await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn sleep<SN: Sleeper>(self, sleep_fn: SN) -> Retry<B, T, E, Fut, FutureFn, SN, RF, NF, AF> {
Retry {
backoff: self.backoff,
retryable_fn: self.retryable_fn,
notify_fn: self.notify_fn,
future_fn: self.future_fn,
sleep_fn,
adjust_fn: self.adjust_fn,
state: State::Idle,
}
}
/// Set the conditions for retrying.
///
/// If not specified, all errors are considered retryable.
///
/// # Examples
///
/// ```no_run
/// use anyhow::Result;
/// use backon::ExponentialBuilder;
/// use backon::Retryable;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch
/// .retry(ExponentialBuilder::default())
/// .when(|e| e.to_string() == "EOF")
/// .await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn when<RN: FnMut(&E) -> bool>(
self,
retryable: RN,
) -> Retry<B, T, E, Fut, FutureFn, SF, RN, NF, AF> {
Retry {
backoff: self.backoff,
retryable_fn: retryable,
notify_fn: self.notify_fn,
future_fn: self.future_fn,
sleep_fn: self.sleep_fn,
adjust_fn: self.adjust_fn,
state: self.state,
}
}
/// Set to notify for all retry attempts.
///
/// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing.
///
/// If not specified, this operation does nothing.
///
/// # Examples
///
/// ```no_run
/// use core::time::Duration;
///
/// use anyhow::Result;
/// use backon::ExponentialBuilder;
/// use backon::Retryable;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch
/// .retry(ExponentialBuilder::default())
/// .notify(|err: &anyhow::Error, dur: Duration| {
/// println!("retrying error {:?} with sleeping {:?}", err, dur);
/// })
/// .await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn notify<NN: FnMut(&E, Duration)>(
self,
notify: NN,
) -> Retry<B, T, E, Fut, FutureFn, SF, RF, NN, AF> {
Retry {
backoff: self.backoff,
retryable_fn: self.retryable_fn,
notify_fn: notify,
sleep_fn: self.sleep_fn,
future_fn: self.future_fn,
adjust_fn: self.adjust_fn,
state: self.state,
}
}
/// Sets the function to adjust the backoff duration for retry attempts.
///
/// When a retry occurs, the provided function will be called with the error and the proposed backoff duration, allowing you to modify the final duration used.
///
/// If the function returns `None`, it indicates that no further retries should be made, and the error will be returned regardless of the backoff duration provided by the input.
///
/// If no `adjust` function is specified, the original backoff duration from the input will be used without modification.
///
/// `adjust` can be used to implement dynamic backoff strategies, such as adjust backoff values from the http `Retry-After` headers.
///
/// # Examples
///
/// ```no_run
/// use core::time::Duration;
/// use std::error::Error;
/// use std::fmt::Display;
/// use std::fmt::Formatter;
///
/// use anyhow::Result;
/// use backon::ExponentialBuilder;
/// use backon::Retryable;
/// use reqwest::header::HeaderMap;
/// use reqwest::StatusCode;
///
/// #[derive(Debug)]
/// struct HttpError {
/// headers: HeaderMap,
/// }
///
/// impl Display for HttpError {
/// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
/// write!(f, "http error")
/// }
/// }
///
/// impl Error for HttpError {}
///
/// async fn fetch() -> Result<String> {
/// let resp = reqwest::get("https://www.rust-lang.org").await?;
/// if resp.status() != StatusCode::OK {
/// let source = HttpError {
/// headers: resp.headers().clone(),
/// };
/// return Err(anyhow::Error::new(source));
/// }
/// Ok(resp.text().await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch
/// .retry(ExponentialBuilder::default())
/// .adjust(|err, dur| {
/// match err.downcast_ref::<HttpError>() {
/// Some(v) => {
/// if let Some(retry_after) = v.headers.get("Retry-After") {
/// // Parse the Retry-After header and adjust the backoff duration
/// let retry_after = retry_after.to_str().unwrap_or("0");
/// let retry_after = retry_after.parse::<u64>().unwrap_or(0);
/// Some(Duration::from_secs(retry_after))
/// } else {
/// dur
/// }
/// }
/// None => dur,
/// }
/// })
/// .await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn adjust<NAF: FnMut(&E, Option<Duration>) -> Option<Duration>>(
self,
adjust: NAF,
) -> Retry<B, T, E, Fut, FutureFn, SF, RF, NF, NAF> {
Retry {
backoff: self.backoff,
retryable_fn: self.retryable_fn,
notify_fn: self.notify_fn,
sleep_fn: self.sleep_fn,
future_fn: self.future_fn,
adjust_fn: adjust,
state: self.state,
}
}
}
/// State maintains internal state of retry.
#[derive(Default)]
enum State<T, E, Fut: Future<Output = Result<T, E>>, SleepFut: Future> {
#[default]
Idle,
Polling(Fut),
Sleeping(SleepFut),
}
impl<B, T, E, Fut, FutureFn, SF, RF, NF, AF> Future
for Retry<B, T, E, Fut, FutureFn, SF, RF, NF, AF>
where
B: Backoff,
Fut: Future<Output = Result<T, E>>,
FutureFn: FnMut() -> Fut,
SF: Sleeper,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
AF: FnMut(&E, Option<Duration>) -> Option<Duration>,
{
type Output = Result<T, E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safety: This is safe because we don't move the `Retry` struct itself,
// only its internal state.
//
// We do the exactly same thing like `pin_project` but without depending on it directly.
let this = unsafe { self.get_unchecked_mut() };
loop {
match &mut this.state {
State::Idle => {
let fut = (this.future_fn)();
this.state = State::Polling(fut);
continue;
}
State::Polling(fut) => {
// Safety: This is safe because we don't move the `Retry` struct and this fut,
// only its internal state.
//
// We do the exactly same thing like `pin_project` but without depending on it directly.
let mut fut = unsafe { Pin::new_unchecked(fut) };
match ready!(fut.as_mut().poll(cx)) {
Ok(v) => return Poll::Ready(Ok(v)),
Err(err) => {
// If input error is not retryable, return error directly.
if !(this.retryable_fn)(&err) {
return Poll::Ready(Err(err));
}
let adjusted_backoff = (this.adjust_fn)(&err, this.backoff.next());
match adjusted_backoff {
None => return Poll::Ready(Err(err)),
Some(dur) => {
(this.notify_fn)(&err, dur);
this.state = State::Sleeping(this.sleep_fn.sleep(dur));
continue;
}
}
}
}
}
State::Sleeping(sl) => {
// Safety: This is safe because we don't move the `Retry` struct and this fut,
// only its internal state.
//
// We do the exactly same thing like `pin_project` but without depending on it directly.
let mut sl = unsafe { Pin::new_unchecked(sl) };
ready!(sl.as_mut().poll(cx));
this.state = State::Idle;
continue;
}
}
}
}
}
#[cfg(test)]
#[cfg(any(feature = "tokio-sleep", feature = "gloo-timers-sleep",))]
mod default_sleeper_tests {
extern crate alloc;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use core::time::Duration;
use tokio::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
use tokio::test;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
use super::*;
use crate::ExponentialBuilder;
async fn always_error() -> anyhow::Result<()> {
Err(anyhow::anyhow!("test_query meets error"))
}
#[test]
async fn test_retry() {
let result = always_error
.retry(ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)))
.await;
assert!(result.is_err());
assert_eq!("test_query meets error", result.unwrap_err().to_string());
}
#[test]
async fn test_retry_with_not_retryable_error() {
let error_times = Mutex::new(0);
let f = || async {
let mut x = error_times.lock().await;
*x += 1;
Err::<(), anyhow::Error>(anyhow::anyhow!("not retryable"))
};
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(backoff)
// Only retry If error message is `retryable`
.when(|e| e.to_string() == "retryable")
.await;
assert!(result.is_err());
assert_eq!("not retryable", result.unwrap_err().to_string());
// `f` always returns error "not retryable", so it should be executed
// only once.
assert_eq!(*error_times.lock().await, 1);
}
#[test]
async fn test_retry_with_retryable_error() {
let error_times = Mutex::new(0);
let f = || async {
let mut x = error_times.lock().await;
*x += 1;
Err::<(), anyhow::Error>(anyhow::anyhow!("retryable"))
};
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(backoff)
// Only retry If error message is `retryable`
.when(|e| e.to_string() == "retryable")
.await;
assert!(result.is_err());
assert_eq!("retryable", result.unwrap_err().to_string());
// `f` always returns error "retryable", so it should be executed
// 4 times (retry 3 times).
assert_eq!(*error_times.lock().await, 4);
}
#[test]
async fn test_retry_with_adjust() {
let error_times = std::sync::Mutex::new(0);
let f = || async { Err::<(), anyhow::Error>(anyhow::anyhow!("retryable")) };
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(backoff)
// Only retry If error message is `retryable`
.when(|e| e.to_string() == "retryable")
.adjust(|_, dur| {
let mut x = error_times.lock().unwrap();
*x += 1;
dur
})
.await;
assert!(result.is_err());
assert_eq!("retryable", result.unwrap_err().to_string());
// `f` always returns error "retryable", so it should be executed
// 4 times (retry 3 times).
assert_eq!(*error_times.lock().unwrap(), 4);
}
#[test]
async fn test_fn_mut_when_and_notify() {
let mut calls_retryable: Vec<()> = vec![];
let mut calls_notify: Vec<()> = vec![];
let f = || async { Err::<(), anyhow::Error>(anyhow::anyhow!("retryable")) };
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let result = f
.retry(backoff)
.when(|_| {
calls_retryable.push(());
true
})
.notify(|_, _| {
calls_notify.push(());
})
.await;
assert!(result.is_err());
assert_eq!("retryable", result.unwrap_err().to_string());
// `f` always returns error "retryable", so it should be executed
// 4 times (retry 3 times).
assert_eq!(calls_retryable.len(), 4);
assert_eq!(calls_notify.len(), 3);
}
}
#[cfg(test)]
mod custom_sleeper_tests {
extern crate alloc;
use alloc::string::ToString;
use core::future::ready;
use core::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use tokio::test;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
use super::*;
use crate::ExponentialBuilder;
async fn always_error() -> anyhow::Result<()> {
Err(anyhow::anyhow!("test_query meets error"))
}
#[test]
async fn test_retry_with_sleep() {
let result = always_error
.retry(ExponentialBuilder::default().with_min_delay(Duration::from_millis(1)))
.sleep(|_| ready(()))
.await;
assert!(result.is_err());
assert_eq!("test_query meets error", result.unwrap_err().to_string());
}
}

420
vendor/backon/src/retry_with_context.rs vendored Normal file
View File

@@ -0,0 +1,420 @@
use core::future::Future;
use core::pin::Pin;
use core::task::Context;
use core::task::Poll;
use core::task::ready;
use core::time::Duration;
use crate::Backoff;
use crate::DefaultSleeper;
use crate::Sleeper;
use crate::backoff::BackoffBuilder;
use crate::sleep::MaybeSleeper;
/// `RetryableWithContext` adds retry support for functions that produce futures with results
/// and context.
///
/// This means all types implementing `FnMut(Ctx) -> impl Future<Output = (Ctx, Result<T, E>)>`
/// can use `retry`.
///
/// Users must provide context to the function and can receive it back after the retry is completed.
///
/// # Example
///
/// Without context, we might encounter errors such as the following:
///
/// ```shell
/// error: captured variable cannot escape `FnMut` closure body
/// --> src/retry.rs:404:27
/// |
/// 400 | let mut test = Test;
/// | -------- variable defined here
/// ...
/// 404 | let result = { || async { test.hello().await } }
/// | - ^^^^^^^^----^^^^^^^^^^^^^^^^
/// | | | |
/// | | | variable captured here
/// | | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
/// | inferred to be a `FnMut` closure
/// |
/// = note: `FnMut` closures only have access to their captured variables while they are executing...
/// = note: ...therefore, they cannot allow references to captured variables to escape
/// ```
///
/// However, with context support, we can implement it this way:
///
/// ```no_run
/// use anyhow::anyhow;
/// use anyhow::Result;
/// use backon::ExponentialBuilder;
/// use backon::RetryableWithContext;
///
/// struct Test;
///
/// impl Test {
/// async fn hello(&mut self) -> Result<usize> {
/// Err(anyhow!("not retryable"))
/// }
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let mut test = Test;
///
/// // (Test, Result<usize>)
/// let (_, result) = {
/// |mut v: Test| async {
/// let res = v.hello().await;
/// (v, res)
/// }
/// }
/// .retry(ExponentialBuilder::default())
/// .context(test)
/// .await;
///
/// Ok(())
/// }
/// ```
pub trait RetryableWithContext<
B: BackoffBuilder,
T,
E,
Ctx,
Fut: Future<Output = (Ctx, Result<T, E>)>,
FutureFn: FnMut(Ctx) -> Fut,
>
{
/// Generate a new retry
fn retry(self, builder: B) -> RetryWithContext<B::Backoff, T, E, Ctx, Fut, FutureFn>;
}
impl<B, T, E, Ctx, Fut, FutureFn> RetryableWithContext<B, T, E, Ctx, Fut, FutureFn> for FutureFn
where
B: BackoffBuilder,
Fut: Future<Output = (Ctx, Result<T, E>)>,
FutureFn: FnMut(Ctx) -> Fut,
{
fn retry(self, builder: B) -> RetryWithContext<B::Backoff, T, E, Ctx, Fut, FutureFn> {
RetryWithContext::new(self, builder.build())
}
}
/// Retry struct generated by [`RetryableWithContext`].
pub struct RetryWithContext<
B: Backoff,
T,
E,
Ctx,
Fut: Future<Output = (Ctx, Result<T, E>)>,
FutureFn: FnMut(Ctx) -> Fut,
SF: MaybeSleeper = DefaultSleeper,
RF = fn(&E) -> bool,
NF = fn(&E, Duration),
> {
backoff: B,
retryable: RF,
notify: NF,
future_fn: FutureFn,
sleep_fn: SF,
state: State<T, E, Ctx, Fut, SF::Sleep>,
}
impl<B, T, E, Ctx, Fut, FutureFn> RetryWithContext<B, T, E, Ctx, Fut, FutureFn>
where
B: Backoff,
Fut: Future<Output = (Ctx, Result<T, E>)>,
FutureFn: FnMut(Ctx) -> Fut,
{
/// Create a new retry.
fn new(future_fn: FutureFn, backoff: B) -> Self {
RetryWithContext {
backoff,
retryable: |_: &E| true,
notify: |_: &E, _: Duration| {},
future_fn,
sleep_fn: DefaultSleeper::default(),
state: State::Idle(None),
}
}
}
impl<B, T, E, Ctx, Fut, FutureFn, SF, RF, NF>
RetryWithContext<B, T, E, Ctx, Fut, FutureFn, SF, RF, NF>
where
B: Backoff,
Fut: Future<Output = (Ctx, Result<T, E>)>,
FutureFn: FnMut(Ctx) -> Fut,
SF: MaybeSleeper,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
/// Set the sleeper for retrying.
///
/// The sleeper should implement the [`Sleeper`] trait. The simplest way is to use a closure that returns a `Future`.
///
/// If not specified, we use the [`DefaultSleeper`].
pub fn sleep<SN: Sleeper>(
self,
sleep_fn: SN,
) -> RetryWithContext<B, T, E, Ctx, Fut, FutureFn, SN, RF, NF> {
assert!(
matches!(self.state, State::Idle(None)),
"sleep must be set before context"
);
RetryWithContext {
backoff: self.backoff,
retryable: self.retryable,
notify: self.notify,
future_fn: self.future_fn,
sleep_fn,
state: State::Idle(None),
}
}
/// Set the context for retrying.
///
/// Context is used to capture ownership manually to prevent lifetime issues.
pub fn context(
self,
context: Ctx,
) -> RetryWithContext<B, T, E, Ctx, Fut, FutureFn, SF, RF, NF> {
RetryWithContext {
backoff: self.backoff,
retryable: self.retryable,
notify: self.notify,
future_fn: self.future_fn,
sleep_fn: self.sleep_fn,
state: State::Idle(Some(context)),
}
}
/// Set the conditions for retrying.
///
/// If not specified, all errors are considered retryable.
///
/// # Examples
///
/// ```no_run
/// use anyhow::Result;
/// use backon::ExponentialBuilder;
/// use backon::Retryable;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch
/// .retry(ExponentialBuilder::default())
/// .when(|e| e.to_string() == "EOF")
/// .await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn when<RN: FnMut(&E) -> bool>(
self,
retryable: RN,
) -> RetryWithContext<B, T, E, Ctx, Fut, FutureFn, SF, RN, NF> {
RetryWithContext {
backoff: self.backoff,
retryable,
notify: self.notify,
future_fn: self.future_fn,
sleep_fn: self.sleep_fn,
state: self.state,
}
}
/// Set to notify for all retry attempts.
///
/// When a retry happens, the input function will be invoked with the error and the sleep duration before pausing.
///
/// If not specified, this operation does nothing.
///
/// # Examples
///
/// ```no_run
/// use core::time::Duration;
///
/// use anyhow::Result;
/// use backon::ExponentialBuilder;
/// use backon::Retryable;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org")
/// .await?
/// .text()
/// .await?)
/// }
///
/// #[tokio::main(flavor = "current_thread")]
/// async fn main() -> Result<()> {
/// let content = fetch
/// .retry(ExponentialBuilder::default())
/// .notify(|err: &anyhow::Error, dur: Duration| {
/// println!("retrying error {:?} with sleeping {:?}", err, dur);
/// })
/// .await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub fn notify<NN: FnMut(&E, Duration)>(
self,
notify: NN,
) -> RetryWithContext<B, T, E, Ctx, Fut, FutureFn, SF, RF, NN> {
RetryWithContext {
backoff: self.backoff,
retryable: self.retryable,
notify,
future_fn: self.future_fn,
sleep_fn: self.sleep_fn,
state: self.state,
}
}
}
/// State maintains internal state of retry.
enum State<T, E, Ctx, Fut: Future<Output = (Ctx, Result<T, E>)>, SleepFut: Future> {
Idle(Option<Ctx>),
Polling(Fut),
Sleeping((Option<Ctx>, SleepFut)),
}
impl<B, T, E, Ctx, Fut, FutureFn, SF, RF, NF> Future
for RetryWithContext<B, T, E, Ctx, Fut, FutureFn, SF, RF, NF>
where
B: Backoff,
Fut: Future<Output = (Ctx, Result<T, E>)>,
FutureFn: FnMut(Ctx) -> Fut,
SF: Sleeper,
RF: FnMut(&E) -> bool,
NF: FnMut(&E, Duration),
{
type Output = (Ctx, Result<T, E>);
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safety: This is safe because we don't move the `Retry` struct itself,
// only its internal state.
//
// We do the exactly same thing like `pin_project` but without depending on it directly.
let this = unsafe { self.get_unchecked_mut() };
loop {
match &mut this.state {
State::Idle(ctx) => {
let ctx = ctx.take().expect("context must be valid");
let fut = (this.future_fn)(ctx);
this.state = State::Polling(fut);
continue;
}
State::Polling(fut) => {
// Safety: This is safe because we don't move the `Retry` struct and this fut,
// only its internal state.
//
// We do the exactly same thing like `pin_project` but without depending on it directly.
let mut fut = unsafe { Pin::new_unchecked(fut) };
let (ctx, res) = ready!(fut.as_mut().poll(cx));
match res {
Ok(v) => return Poll::Ready((ctx, Ok(v))),
Err(err) => {
// If input error is not retryable, return error directly.
if !(this.retryable)(&err) {
return Poll::Ready((ctx, Err(err)));
}
match this.backoff.next() {
None => return Poll::Ready((ctx, Err(err))),
Some(dur) => {
(this.notify)(&err, dur);
this.state =
State::Sleeping((Some(ctx), this.sleep_fn.sleep(dur)));
continue;
}
}
}
}
}
State::Sleeping((ctx, sl)) => {
// Safety: This is safe because we don't move the `Retry` struct and this fut,
// only its internal state.
//
// We do the exactly same thing like `pin_project` but without depending on it directly.
let mut sl = unsafe { Pin::new_unchecked(sl) };
ready!(sl.as_mut().poll(cx));
let ctx = ctx.take().expect("context must be valid");
this.state = State::Idle(Some(ctx));
continue;
}
}
}
}
}
#[cfg(test)]
#[cfg(any(feature = "tokio-sleep", feature = "gloo-timers-sleep",))]
mod tests {
extern crate alloc;
use alloc::string::ToString;
use core::time::Duration;
use anyhow::Result;
use anyhow::anyhow;
use tokio::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
use tokio::test;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
use super::*;
use crate::ExponentialBuilder;
struct Test;
impl Test {
async fn hello(&mut self) -> Result<usize> {
Err(anyhow!("not retryable"))
}
}
#[test]
async fn test_retry_with_not_retryable_error() {
let error_times = Mutex::new(0);
let test = Test;
let backoff = ExponentialBuilder::default().with_min_delay(Duration::from_millis(1));
let (_, result) = {
|mut v: Test| async {
let mut x = error_times.lock().await;
*x += 1;
let res = v.hello().await;
(v, res)
}
}
.retry(backoff)
.context(test)
// Only retry If error message is `retryable`
.when(|e| e.to_string() == "retryable")
.await;
assert!(result.is_err());
assert_eq!("not retryable", result.unwrap_err().to_string());
// `f` always returns error "not retryable", so it should be executed
// only once.
assert_eq!(*error_times.lock().await, 1);
}
}

111
vendor/backon/src/sleep.rs vendored Normal file
View File

@@ -0,0 +1,111 @@
use core::future::Future;
use core::future::Ready;
use core::time::Duration;
/// A sleeper is used to generate a future that completes after a specified duration.
pub trait Sleeper: 'static {
/// The future returned by the `sleep` method.
type Sleep: Future;
/// Create a future that completes after a set period.
fn sleep(&self, dur: Duration) -> Self::Sleep;
}
/// A stub trait allowing non-[`Sleeper`] types to be used as a generic parameter in [`Retry`][crate::Retry].
/// It does not provide actual functionality.
#[doc(hidden)]
pub trait MaybeSleeper: 'static {
type Sleep: Future;
}
/// All `Sleeper` will implement `MaybeSleeper`, but not vice versa.
impl<T: Sleeper + ?Sized> MaybeSleeper for T {
type Sleep = <T as Sleeper>::Sleep;
}
/// All `Fn(Duration) -> impl Future` implements `Sleeper`.
impl<F: Fn(Duration) -> Fut + 'static, Fut: Future> Sleeper for F {
type Sleep = Fut;
fn sleep(&self, dur: Duration) -> Self::Sleep {
self(dur)
}
}
/// The default implementation of `Sleeper` when no features are enabled.
///
/// It will fail to compile if a containing [`Retry`][crate::Retry] is `.await`ed without calling [`Retry::sleep`][crate::Retry::sleep] to provide a valid sleeper.
#[cfg(all(not(feature = "tokio-sleep"), not(feature = "gloo-timers-sleep"),))]
pub type DefaultSleeper = PleaseEnableAFeatureOrProvideACustomSleeper;
/// The default implementation of `Sleeper` while feature `tokio-sleep` enabled.
///
/// it uses `tokio::time::sleep`.
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))]
pub type DefaultSleeper = TokioSleeper;
/// The default implementation of `Sleeper` while feature `gloo-timers-sleep` enabled.
///
/// It uses `gloo_timers::sleep::sleep`.
#[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))]
pub type DefaultSleeper = GlooTimersSleep;
/// A placeholder type that does not implement [`Sleeper`] and will therefore fail to compile if used as one.
///
/// Users should enable a feature of this crate that provides a valid [`Sleeper`] implementation when this type appears in compilation errors. Alternatively, a custom [`Sleeper`] implementation should be provided where necessary, such as in [`crate::Retry::sleeper`].
#[doc(hidden)]
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Default)]
pub struct PleaseEnableAFeatureOrProvideACustomSleeper;
/// Implement `MaybeSleeper` but not `Sleeper`.
impl MaybeSleeper for PleaseEnableAFeatureOrProvideACustomSleeper {
type Sleep = Ready<()>;
}
/// The default implementation of `Sleeper` uses `tokio::time::sleep`.
///
/// It will adhere to [pausing/auto-advancing](https://docs.rs/tokio/latest/tokio/time/fn.pause.html)
/// in Tokio's Runtime semantics, if enabled.
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))]
#[derive(Clone, Copy, Debug, Default)]
pub struct TokioSleeper;
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio-sleep"))]
impl Sleeper for TokioSleeper {
type Sleep = tokio::time::Sleep;
fn sleep(&self, dur: Duration) -> Self::Sleep {
tokio::time::sleep(dur)
}
}
/// The implementation of `Sleeper` that uses `futures_timer::Delay`.
///
/// This implementation is based on
/// the [`futures-timer`](https://docs.rs/futures-timer/latest/futures_timer/) crate.
/// It is async runtime agnostic and will also work in WASM environments.
#[cfg(feature = "futures-timer-sleep")]
#[derive(Clone, Copy, Debug, Default)]
pub struct FuturesTimerSleeper;
#[cfg(feature = "futures-timer-sleep")]
impl Sleeper for FuturesTimerSleeper {
type Sleep = futures_timer::Delay;
fn sleep(&self, dur: Duration) -> Self::Sleep {
futures_timer::Delay::new(dur)
}
}
/// The default implementation of `Sleeper` utilizes `gloo_timers::future::sleep`.
#[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))]
#[derive(Clone, Copy, Debug, Default)]
pub struct GlooTimersSleep;
#[cfg(all(target_arch = "wasm32", feature = "gloo-timers-sleep"))]
impl Sleeper for GlooTimersSleep {
type Sleep = gloo_timers::future::TimeoutFuture;
fn sleep(&self, dur: Duration) -> Self::Sleep {
gloo_timers::future::sleep(dur)
}
}