Skip to content

Commit 562ecc1

Browse files
martintmkCopilot
andauthored
feat(seatbelt): add handle_unavailable to RetryConfig and HedgingConfig (#314)
Add the handle_unavailable boolean field to both config structs so it can be driven from external configuration sources (e.g. config files). The field defaults to false, matching the existing layer default. The config() methods on RetryLayer and HedgingLayer now forward the new field, and all snapshot tests are updated accordingly. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a9ae403 commit 562ecc1

15 files changed

Lines changed: 48 additions & 31 deletions

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ layered = { path = "crates/layered", default-features = false, version = "0.3.0"
4141
ohno = { path = "crates/ohno", default-features = false, version = "0.3.1" }
4242
ohno_macros = { path = "crates/ohno_macros", default-features = false, version = "0.3.0" }
4343
recoverable = { path = "crates/recoverable", default-features = false, version = "0.1.1" }
44-
seatbelt = { path = "crates/seatbelt", default-features = false, version = "0.4.1" }
44+
seatbelt = { path = "crates/seatbelt", default-features = false, version = "0.4.2" }
4545
templated_uri = { path = "crates/templated_uri", default-features = false, version = "0.1.0" }
4646
templated_uri_macros = { path = "crates/templated_uri_macros", default-features = false, version = "0.1.0" }
4747
templated_uri_macros_impl = { path = "crates/templated_uri_macros_impl", default-features = false, version = "0.1.0" }

crates/seatbelt/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [0.4.2] - 2026-03-10
4+
5+
- ✨ Features
6+
7+
- add `RetryConfig::handle_unavailable` and `HedgingConfig::handle_unavailable`
8+
39
## [0.4.1] - 2026-03-10
410

511
- ✨ Features

crates/seatbelt/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[package]
55
name = "seatbelt"
66
description = "Resilience and recovery mechanisms for fallible operations."
7-
version = "0.4.1"
7+
version = "0.4.2"
88
readme = "README.md"
99
keywords = ["oxidizer", "resilience", "layered", "recovery", "retry"]
1010
categories = ["data-structures"]

crates/seatbelt/README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,12 @@ This crate provides several optional features that can be enabled in your `Cargo
182182
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/oxidizer/tree/main/crates/seatbelt">source code</a>.
183183
</sub>
184184

185-
[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG4STOoP2_1kjG_YFfNmBbFbQG7vz1CXQ1d10GxetSJWWkufEYWSFgmdsYXllcmVkZTAuMy4wgmtyZWNvdmVyYWJsZWUwLjEuMYJoc2VhdGJlbHRlMC40LjGCZHRpY2tlMC4yLjGCbXRvd2VyX3NlcnZpY2VlMC4zLjM
185+
[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG4STOoP2_1kjG_YFfNmBbFbQG7vz1CXQ1d10GxetSJWWkufEYWSFgmdsYXllcmVkZTAuMy4wgmtyZWNvdmVyYWJsZWUwLjEuMYJoc2VhdGJlbHRlMC40LjKCZHRpY2tlMC4yLjGCbXRvd2VyX3NlcnZpY2VlMC4zLjM
186186
[__link0]: https://crates.io/crates/layered/0.3.0
187187
[__link1]: https://docs.rs/layered/0.3.0/layered/?search=Stack
188-
[__link10]: https://docs.rs/seatbelt/0.4.1/seatbelt/hedging/index.html
189-
[__link11]: https://docs.rs/seatbelt/0.4.1/seatbelt/breaker/index.html
190-
[__link12]: https://docs.rs/seatbelt/0.4.1/seatbelt/fallback/index.html
188+
[__link10]: https://docs.rs/seatbelt/0.4.2/seatbelt/hedging/index.html
189+
[__link11]: https://docs.rs/seatbelt/0.4.2/seatbelt/breaker/index.html
190+
[__link12]: https://docs.rs/seatbelt/0.4.2/seatbelt/fallback/index.html
191191
[__link13]: https://github.com/microsoft/oxidizer/blob/main/crates/seatbelt/examples/timeout.rs
192192
[__link14]: https://github.com/microsoft/oxidizer/blob/main/crates/seatbelt/examples/timeout_advanced.rs
193193
[__link15]: https://github.com/microsoft/oxidizer/blob/main/crates/seatbelt/examples/retry.rs
@@ -201,16 +201,16 @@ This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Br
201201
[__link22]: https://github.com/microsoft/oxidizer/blob/main/crates/seatbelt/examples/tower.rs
202202
[__link23]: https://github.com/microsoft/oxidizer/blob/main/crates/seatbelt/examples/config.rs
203203
[__link24]: https://github.com/microsoft/oxidizer/blob/main/crates/seatbelt/examples/config.json
204-
[__link25]: https://docs.rs/seatbelt/0.4.1/seatbelt/timeout/index.html
205-
[__link26]: https://docs.rs/seatbelt/0.4.1/seatbelt/retry/index.html
206-
[__link27]: https://docs.rs/seatbelt/0.4.1/seatbelt/hedging/index.html
207-
[__link28]: https://docs.rs/seatbelt/0.4.1/seatbelt/breaker/index.html
208-
[__link29]: https://docs.rs/seatbelt/0.4.1/seatbelt/fallback/index.html
204+
[__link25]: https://docs.rs/seatbelt/0.4.2/seatbelt/timeout/index.html
205+
[__link26]: https://docs.rs/seatbelt/0.4.2/seatbelt/retry/index.html
206+
[__link27]: https://docs.rs/seatbelt/0.4.2/seatbelt/hedging/index.html
207+
[__link28]: https://docs.rs/seatbelt/0.4.2/seatbelt/breaker/index.html
208+
[__link29]: https://docs.rs/seatbelt/0.4.2/seatbelt/fallback/index.html
209209
[__link3]: https://crates.io/crates/tick/0.2.1
210210
[__link30]: https://docs.rs/tower_service/0.3.3/tower_service/?search=Service
211-
[__link4]: https://docs.rs/seatbelt/0.4.1/seatbelt/?search=ResilienceContext
212-
[__link5]: https://docs.rs/seatbelt/0.4.1/seatbelt/?search=ResilienceContext
211+
[__link4]: https://docs.rs/seatbelt/0.4.2/seatbelt/?search=ResilienceContext
212+
[__link5]: https://docs.rs/seatbelt/0.4.2/seatbelt/?search=ResilienceContext
213213
[__link6]: https://docs.rs/recoverable/0.1.1/recoverable/?search=RecoveryInfo
214214
[__link7]: https://docs.rs/recoverable/0.1.1/recoverable/?search=Recovery
215-
[__link8]: https://docs.rs/seatbelt/0.4.1/seatbelt/timeout/index.html
216-
[__link9]: https://docs.rs/seatbelt/0.4.1/seatbelt/retry/index.html
215+
[__link8]: https://docs.rs/seatbelt/0.4.2/seatbelt/timeout/index.html
216+
[__link9]: https://docs.rs/seatbelt/0.4.2/seatbelt/retry/index.html

crates/seatbelt/examples/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"base_delay": "100ms",
66
"max_delay": "5s",
77
"use_jitter": true,
8-
"max_retry_attempts": 5
8+
"max_retry_attempts": 5,
9+
"handle_unavailable": false
910
},
1011
"breaker": {
1112
"enabled": true,

crates/seatbelt/src/hedging/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::hedging::constants::{DEFAULT_HEDGING_DELAY, DEFAULT_MAX_HEDGED_ATTEMP
2020
/// | `enabled` | `true` |
2121
/// | `hedging_delay` | 500 milliseconds |
2222
/// | `max_hedged_attempts` | `1` |
23+
/// | `handle_unavailable` | `false` |
2324
#[derive(Debug, Clone, PartialEq)]
2425
#[cfg_attr(any(feature = "serde", test), derive(serde::Serialize, serde::Deserialize))]
2526
#[non_exhaustive]
@@ -37,6 +38,11 @@ pub struct HedgingConfig {
3738

3839
/// The maximum number of additional hedged attempts (not counting the original call).
3940
pub max_hedged_attempts: u8,
41+
42+
/// Whether to treat [`RecoveryInfo::unavailable()`][crate::RecoveryInfo::unavailable]
43+
/// classifications as recoverable conditions, allowing continued hedging against
44+
/// in-flight requests. When `false`, unavailable responses are returned immediately.
45+
pub handle_unavailable: bool,
4046
}
4147

4248
impl Default for HedgingConfig {
@@ -45,6 +51,7 @@ impl Default for HedgingConfig {
4551
enabled: true,
4652
hedging_delay: DEFAULT_HEDGING_DELAY,
4753
max_hedged_attempts: DEFAULT_MAX_HEDGED_ATTEMPTS,
54+
handle_unavailable: false,
4855
}
4956
}
5057
}

crates/seatbelt/src/hedging/layer.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ impl<In, Out, S1, S2> HedgingLayer<In, Out, S1, S2> {
131131
pub fn config(self, config: &HedgingConfig) -> Self {
132132
self.hedging_delay(config.hedging_delay)
133133
.max_hedged_attempts(config.max_hedged_attempts)
134+
.handle_unavailable(config.handle_unavailable)
134135
.enable(config.enabled)
135136
}
136137

@@ -467,6 +468,7 @@ mod tests {
467468
enabled: false,
468469
hedging_delay: Duration::from_secs(2),
469470
max_hedged_attempts: 4,
471+
handle_unavailable: true,
470472
};
471473

472474
let layer = create_ready_layer().config(&config);

crates/seatbelt/src/hedging/snapshots/seatbelt__hedging__config__tests__default_snapshot.snap

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ expression: config
55
{
66
"enabled": true,
77
"hedging_delay": "500ms",
8-
"max_hedged_attempts": 1
8+
"max_hedged_attempts": 1,
9+
"handle_unavailable": false
910
}

crates/seatbelt/src/hedging/snapshots/seatbelt__hedging__layer__tests__config_applies_all_settings.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ HedgingLayer {
2626
ShouldRecover,
2727
),
2828
on_execute: None,
29-
handle_unavailable: false,
29+
handle_unavailable: true,
3030
enable_if: Disabled,
3131
telemetry: TelemetryHelper {
3232
pipeline_name: "test_pipeline",

0 commit comments

Comments
 (0)