From 01fd5273201e9627888f2bdea03cbe656c412785 Mon Sep 17 00:00:00 2001 From: Xin Sun Date: Wed, 19 Aug 2026 20:06:38 +0800 Subject: [PATCH 1/6] feat(layers/retry): add maximum elapsed time Allow callers to bound each retry episode by elapsed time while preserving in-flight attempts and stateful operation behavior. --- core/Cargo.lock | 13 +++- core/layers/retry/Cargo.toml | 2 +- core/layers/retry/src/lib.rs | 143 +++++++++++++++++++++++++++++++++-- 3 files changed, 148 insertions(+), 10 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 68fa68fc13df..8932c8fd4f5a 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1045,10 +1045,19 @@ name = "backon" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "git+https://github.com/ddupg/backon.git?rev=7eeed718cb85bb89752194797cbbcc85e3271299#7eeed718cb85bb89752194797cbbcc85e3271299" dependencies = [ "fastrand", "gloo-timers", "tokio", + "web-time", ] [[package]] @@ -6730,7 +6739,7 @@ dependencies = [ name = "opendal-layer-retry" version = "0.58.2" dependencies = [ - "backon", + "backon 1.6.0 (git+https://github.com/ddupg/backon.git?rev=7eeed718cb85bb89752194797cbbcc85e3271299)", "bytes", "futures", "log", @@ -9057,7 +9066,7 @@ dependencies = [ "arc-swap", "arcstr", "async-lock", - "backon", + "backon 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes", "cfg-if 1.0.4", "combine", diff --git a/core/layers/retry/Cargo.toml b/core/layers/retry/Cargo.toml index 55302e9dd3bc..c04ec40fc015 100644 --- a/core/layers/retry/Cargo.toml +++ b/core/layers/retry/Cargo.toml @@ -32,7 +32,7 @@ version = { workspace = true } all-features = true [dependencies] -backon = { version = "1.6", features = ["tokio-sleep"] } +backon = { git = "https://github.com/ddupg/backon.git", rev = "7eeed718cb85bb89752194797cbbcc85e3271299", features = ["tokio-sleep"] } log = { workspace = true } opendal-core = { path = "../../core", version = "0.58.2", default-features = false } diff --git a/core/layers/retry/src/lib.rs b/core/layers/retry/src/lib.rs index 7371c83bf14f..c66a8c15abe6 100644 --- a/core/layers/retry/src/lib.rs +++ b/core/layers/retry/src/lib.rs @@ -115,6 +115,7 @@ use opendal_core::*; /// ``` pub struct RetryLayer { builder: ExponentialBuilder, + max_elapsed_time: Option, notify: Arc, } @@ -122,6 +123,7 @@ impl Debug for RetryLayer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RetryLayer") .field("builder", &self.builder) + .field("max_elapsed_time", &self.max_elapsed_time) .finish_non_exhaustive() } } @@ -130,6 +132,7 @@ impl Clone for RetryLayer { fn clone(&self) -> Self { Self { builder: self.builder, + max_elapsed_time: self.max_elapsed_time, notify: self.notify.clone(), } } @@ -139,6 +142,7 @@ impl Default for RetryLayer { fn default() -> Self { Self { builder: ExponentialBuilder::default(), + max_elapsed_time: None, notify: Arc::new(DefaultRetryInterceptor), } } @@ -168,6 +172,7 @@ impl RetryLayer { pub fn with_notify(self, notify: NI) -> RetryLayer { RetryLayer { builder: self.builder, + max_elapsed_time: self.max_elapsed_time, notify: Arc::new(notify), } } @@ -212,6 +217,16 @@ impl RetryLayer { self.builder = self.builder.with_max_times(max_times); self } + + /// Set the maximum elapsed time for each retry operation. + /// + /// The timer starts when an operation makes its first attempt. Once the elapsed time reaches + /// this limit, the layer returns the latest error instead of scheduling another retry. It does + /// not interrupt an attempt or retry sleep that is already in progress. + pub fn with_max_elapsed_time(mut self, max_elapsed_time: Duration) -> Self { + self.max_elapsed_time = Some(max_elapsed_time); + self + } } impl Layer for RetryLayer { @@ -226,6 +241,7 @@ impl RetryLayer { inner, notify: self.notify.clone(), builder: self.builder, + max_elapsed_time: self.max_elapsed_time, } } } @@ -282,6 +298,7 @@ pub struct RetryService { inner: Servicer, notify: Arc, builder: ExponentialBuilder, + max_elapsed_time: Option, } impl Debug for RetryService { @@ -316,6 +333,7 @@ impl Service for RetryService { let mut attempt: u32 = 0; { || self.inner.create_dir(ctx, path, args.clone()) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -334,6 +352,7 @@ impl Service for RetryService { let mut attempt: u32 = 0; let reader = { || self.inner.read(ctx, path, args.clone()) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -347,13 +366,19 @@ impl Service for RetryService { .call() .map_err(|err| err.set_persistent())?; - Ok(RetryReader::new(reader, self.notify.clone(), self.builder)) + Ok(RetryReader::new( + reader, + self.notify.clone(), + self.builder, + self.max_elapsed_time, + )) } fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result { let mut attempt: u32 = 0; let writer = { || self.inner.write(ctx, path, args.clone()) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -367,13 +392,19 @@ impl Service for RetryService { .call() .map_err(|err| err.set_persistent())?; - Ok(RetryWrapper::new(writer, self.notify.clone(), self.builder)) + Ok(RetryWrapper::new( + writer, + self.notify.clone(), + self.builder, + self.max_elapsed_time, + )) } async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result { let mut attempt: u32 = 0; { || self.inner.stat(ctx, path, args.clone()) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -392,6 +423,7 @@ impl Service for RetryService { let mut attempt: u32 = 0; let deleter = { || self.inner.delete(ctx) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -409,6 +441,7 @@ impl Service for RetryService { deleter, self.notify.clone(), self.builder, + self.max_elapsed_time, )) } @@ -423,6 +456,7 @@ impl Service for RetryService { let mut attempt: u32 = 0; let copier = { || self.inner.copy(ctx, from, to, args.clone(), opts.clone()) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -436,7 +470,12 @@ impl Service for RetryService { .call() .map_err(|err| err.set_persistent())?; - Ok(RetryWrapper::new(copier, self.notify.clone(), self.builder)) + Ok(RetryWrapper::new( + copier, + self.notify.clone(), + self.builder, + self.max_elapsed_time, + )) } async fn rename( @@ -449,6 +488,7 @@ impl Service for RetryService { let mut attempt: u32 = 0; { || self.inner.rename(ctx, from, to, args.clone()) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -467,6 +507,7 @@ impl Service for RetryService { let mut attempt: u32 = 0; let lister = { || self.inner.list(ctx, path, args.clone()) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -480,7 +521,12 @@ impl Service for RetryService { .call() .map_err(|err| err.set_persistent())?; - Ok(RetryWrapper::new(lister, self.notify.clone(), self.builder)) + Ok(RetryWrapper::new( + lister, + self.notify.clone(), + self.builder, + self.max_elapsed_time, + )) } async fn presign( @@ -492,6 +538,7 @@ impl Service for RetryService { let mut attempt: u32 = 0; { || self.inner.presign(ctx, path, args.clone()) } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .notify(|err, dur| { attempt += 1; @@ -512,14 +559,21 @@ pub struct RetryReader { inner: Arc, notify: Arc, builder: ExponentialBuilder, + max_elapsed_time: Option, } impl RetryReader { - fn new(inner: R, notify: Arc, builder: ExponentialBuilder) -> Self { + fn new( + inner: R, + notify: Arc, + builder: ExponentialBuilder, + max_elapsed_time: Option, + ) -> Self { Self { inner: Arc::new(inner), notify, builder, + max_elapsed_time, } } } @@ -531,6 +585,7 @@ impl oio::Read for RetryReader oio::Read for RetryReader, )) } @@ -562,6 +618,7 @@ impl oio::Read for RetryReader { read: u64, notify: Arc, builder: ExponentialBuilder, + max_elapsed_time: Option, } impl RetryReadStream { @@ -594,6 +652,7 @@ impl RetryReadStream { range: BytesRange, notify: Arc, builder: ExponentialBuilder, + max_elapsed_time: Option, ) -> Self { Self { reader, @@ -602,6 +661,7 @@ impl RetryReadStream { read: 0, notify, builder, + max_elapsed_time, } } } @@ -652,6 +712,7 @@ impl oio::ReadStream for RetryReadStream { notify: Arc, builder: ExponentialBuilder, + max_elapsed_time: Option, } impl RetryWrapper { - fn new(inner: R, notify: Arc, backoff: ExponentialBuilder) -> Self { + fn new( + inner: R, + notify: Arc, + backoff: ExponentialBuilder, + max_elapsed_time: Option, + ) -> Self { Self { inner: Some(inner), notify, builder: backoff, + max_elapsed_time, } } @@ -715,6 +783,7 @@ impl oio::ReadStream for RetryWrapper oio::Write for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context((inner, bs)) .notify(|err, dur| { @@ -779,6 +849,7 @@ impl oio::Write for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context(inner) .notify(|err, dur| { @@ -810,6 +881,7 @@ impl oio::Write for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context(inner) .notify(|err, dur| { @@ -843,6 +915,7 @@ impl oio::List for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context(inner) .notify(|err, dur| { @@ -881,6 +954,7 @@ impl oio::Delete for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context(inner) .notify(|err, dur| { @@ -912,6 +986,7 @@ impl oio::Delete for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context(inner) .notify(|err, dur| { @@ -945,6 +1020,7 @@ impl oio::Copy for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context(inner) .notify(|err, dur| { @@ -976,6 +1052,7 @@ impl oio::Copy for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context(inner) .notify(|err, dur| { @@ -1007,6 +1084,7 @@ impl oio::Copy for RetryWrapper { } } .retry(self.builder) + .with_max_elapsed_time(self.max_elapsed_time) .when(|e| e.is_temporary()) .context(inner) .notify(|err, dur| { @@ -1122,7 +1200,14 @@ mod tests { )) } - async fn stat(&self, _: &OperationContext, _: &str, _: OpStat) -> Result { + async fn stat(&self, _: &OperationContext, path: &str, _: OpStat) -> Result { + if path == "retryable_error" { + *self.attempt.lock().unwrap() += 1; + return Err( + Error::new(ErrorKind::Unexpected, "retryable_error from stat").set_temporary(), + ); + } + Ok(RpStat::new( Metadata::new(EntryMode::FILE).with_content_length(13), )) @@ -1393,6 +1478,49 @@ mod tests { Ok(()) } + #[tokio::test] + async fn max_elapsed_time_zero_should_not_retry_stat() -> Result<()> { + let builder = MockBuilder::default(); + let op = Operator::new(builder.clone())?.layer( + RetryLayer::default() + .with_max_times(10) + .with_max_elapsed_time(Duration::ZERO), + ); + + let result = op.stat("retryable_error").await; + + assert!(result.is_err()); + assert_eq!(*builder.attempt.lock().unwrap(), 1); + Ok(()) + } + + #[tokio::test] + async fn max_elapsed_time_zero_should_not_retry_writer_close() -> Result<()> { + #[derive(Clone, Default)] + struct Recorder { + retries: Arc>, + } + + impl RetryInterceptor for Recorder { + fn intercept(&self, _: RetryEvent<'_>) { + *self.retries.lock().unwrap() += 1; + } + } + + let recorder = Recorder::default(); + let op = Operator::new(MockBuilder::default())?.layer( + RetryLayer::default() + .with_max_times(10) + .with_max_elapsed_time(Duration::ZERO) + .with_notify(recorder.clone()), + ); + let result = op.writer("test").await?.close().await; + + assert!(result.is_err()); + assert_eq!(*recorder.retries.lock().unwrap(), 0); + Ok(()) + } + /// This test is used to reproduce the panic issue while composing retry layer with timeout layer. #[tokio::test] async fn test_retry_write_fail_on_close() -> Result<()> { @@ -1511,6 +1639,7 @@ mod tests { ExponentialBuilder::default() .with_min_delay(Duration::from_millis(1)) .with_max_delay(Duration::from_millis(1)), + None, ); let (_, mut stream) = oio::Read::open(&reader, BytesRange::default()).await?; From e0681b0243f4c10ea76e02be01c083d9450a472e Mon Sep 17 00:00:00 2001 From: Xin Sun Date: Wed, 19 Aug 2026 20:40:04 +0800 Subject: [PATCH 2/6] test(layers/retry): align elapsed retry checks Track the deterministic BackON tests and use the repository's test naming convention for elapsed retry coverage. --- core/Cargo.lock | 4 ++-- core/layers/retry/Cargo.toml | 2 +- core/layers/retry/src/lib.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 8932c8fd4f5a..9c3d5b1d16b5 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1052,7 +1052,7 @@ dependencies = [ [[package]] name = "backon" version = "1.6.0" -source = "git+https://github.com/ddupg/backon.git?rev=7eeed718cb85bb89752194797cbbcc85e3271299#7eeed718cb85bb89752194797cbbcc85e3271299" +source = "git+https://github.com/ddupg/backon.git?rev=7f084670950ee31a4ccaba2af60a8fdcc4abcd0d#7f084670950ee31a4ccaba2af60a8fdcc4abcd0d" dependencies = [ "fastrand", "gloo-timers", @@ -6739,7 +6739,7 @@ dependencies = [ name = "opendal-layer-retry" version = "0.58.2" dependencies = [ - "backon 1.6.0 (git+https://github.com/ddupg/backon.git?rev=7eeed718cb85bb89752194797cbbcc85e3271299)", + "backon 1.6.0 (git+https://github.com/ddupg/backon.git?rev=7f084670950ee31a4ccaba2af60a8fdcc4abcd0d)", "bytes", "futures", "log", diff --git a/core/layers/retry/Cargo.toml b/core/layers/retry/Cargo.toml index c04ec40fc015..fff0b512fa51 100644 --- a/core/layers/retry/Cargo.toml +++ b/core/layers/retry/Cargo.toml @@ -32,7 +32,7 @@ version = { workspace = true } all-features = true [dependencies] -backon = { git = "https://github.com/ddupg/backon.git", rev = "7eeed718cb85bb89752194797cbbcc85e3271299", features = ["tokio-sleep"] } +backon = { git = "https://github.com/ddupg/backon.git", rev = "7f084670950ee31a4ccaba2af60a8fdcc4abcd0d", features = ["tokio-sleep"] } log = { workspace = true } opendal-core = { path = "../../core", version = "0.58.2", default-features = false } diff --git a/core/layers/retry/src/lib.rs b/core/layers/retry/src/lib.rs index c66a8c15abe6..bedc4bdeb01c 100644 --- a/core/layers/retry/src/lib.rs +++ b/core/layers/retry/src/lib.rs @@ -1479,7 +1479,7 @@ mod tests { } #[tokio::test] - async fn max_elapsed_time_zero_should_not_retry_stat() -> Result<()> { + async fn test_max_elapsed_time_stat() -> Result<()> { let builder = MockBuilder::default(); let op = Operator::new(builder.clone())?.layer( RetryLayer::default() @@ -1495,7 +1495,7 @@ mod tests { } #[tokio::test] - async fn max_elapsed_time_zero_should_not_retry_writer_close() -> Result<()> { + async fn test_max_elapsed_time_writer_close() -> Result<()> { #[derive(Clone, Default)] struct Recorder { retries: Arc>, From 354eedc04ca0284b9cb02475407a2ac8338fe730 Mon Sep 17 00:00:00 2001 From: Xin Sun Date: Wed, 19 Aug 2026 21:13:24 +0800 Subject: [PATCH 3/6] chore(layers/retry): update BackON revision Track the latest retry elapsed-time test configuration from the BackON feature branch. --- core/Cargo.lock | 4 ++-- core/layers/retry/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 9c3d5b1d16b5..59ba95fd4c68 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1052,7 +1052,7 @@ dependencies = [ [[package]] name = "backon" version = "1.6.0" -source = "git+https://github.com/ddupg/backon.git?rev=7f084670950ee31a4ccaba2af60a8fdcc4abcd0d#7f084670950ee31a4ccaba2af60a8fdcc4abcd0d" +source = "git+https://github.com/ddupg/backon.git?rev=e5c333bdfa6598b0644133b023c72c4d16a1acc0#e5c333bdfa6598b0644133b023c72c4d16a1acc0" dependencies = [ "fastrand", "gloo-timers", @@ -6739,7 +6739,7 @@ dependencies = [ name = "opendal-layer-retry" version = "0.58.2" dependencies = [ - "backon 1.6.0 (git+https://github.com/ddupg/backon.git?rev=7f084670950ee31a4ccaba2af60a8fdcc4abcd0d)", + "backon 1.6.0 (git+https://github.com/ddupg/backon.git?rev=e5c333bdfa6598b0644133b023c72c4d16a1acc0)", "bytes", "futures", "log", diff --git a/core/layers/retry/Cargo.toml b/core/layers/retry/Cargo.toml index fff0b512fa51..d3b4506df6be 100644 --- a/core/layers/retry/Cargo.toml +++ b/core/layers/retry/Cargo.toml @@ -32,7 +32,7 @@ version = { workspace = true } all-features = true [dependencies] -backon = { git = "https://github.com/ddupg/backon.git", rev = "7f084670950ee31a4ccaba2af60a8fdcc4abcd0d", features = ["tokio-sleep"] } +backon = { git = "https://github.com/ddupg/backon.git", rev = "e5c333bdfa6598b0644133b023c72c4d16a1acc0", features = ["tokio-sleep"] } log = { workspace = true } opendal-core = { path = "../../core", version = "0.58.2", default-features = false } From 6f61a3ea91bed51482fa51cb158f37d63f6fd79a Mon Sep 17 00:00:00 2001 From: Xin Sun Date: Thu, 20 Aug 2026 09:52:11 +0800 Subject: [PATCH 4/6] chore(layers/retry): refresh BackON revision Track the commit that registers elapsed retry tests with the WASM runner. --- core/Cargo.lock | 4 ++-- core/layers/retry/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 59ba95fd4c68..77afb09ed760 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -1052,7 +1052,7 @@ dependencies = [ [[package]] name = "backon" version = "1.6.0" -source = "git+https://github.com/ddupg/backon.git?rev=e5c333bdfa6598b0644133b023c72c4d16a1acc0#e5c333bdfa6598b0644133b023c72c4d16a1acc0" +source = "git+https://github.com/ddupg/backon.git?rev=bd64facb90cae9983a12f5ec59c71dd2803f6342#bd64facb90cae9983a12f5ec59c71dd2803f6342" dependencies = [ "fastrand", "gloo-timers", @@ -6739,7 +6739,7 @@ dependencies = [ name = "opendal-layer-retry" version = "0.58.2" dependencies = [ - "backon 1.6.0 (git+https://github.com/ddupg/backon.git?rev=e5c333bdfa6598b0644133b023c72c4d16a1acc0)", + "backon 1.6.0 (git+https://github.com/ddupg/backon.git?rev=bd64facb90cae9983a12f5ec59c71dd2803f6342)", "bytes", "futures", "log", diff --git a/core/layers/retry/Cargo.toml b/core/layers/retry/Cargo.toml index d3b4506df6be..280bfd3eb934 100644 --- a/core/layers/retry/Cargo.toml +++ b/core/layers/retry/Cargo.toml @@ -32,7 +32,7 @@ version = { workspace = true } all-features = true [dependencies] -backon = { git = "https://github.com/ddupg/backon.git", rev = "e5c333bdfa6598b0644133b023c72c4d16a1acc0", features = ["tokio-sleep"] } +backon = { git = "https://github.com/ddupg/backon.git", rev = "bd64facb90cae9983a12f5ec59c71dd2803f6342", features = ["tokio-sleep"] } log = { workspace = true } opendal-core = { path = "../../core", version = "0.58.2", default-features = false } From 66922da9337e488f6baf3a5e681bdba8eeebd39f Mon Sep 17 00:00:00 2001 From: Xin Sun Date: Thu, 20 Aug 2026 09:53:59 +0800 Subject: [PATCH 5/6] style(layers/retry): format BackON dependency Apply the repository Taplo formatting required by CI. --- core/layers/retry/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/layers/retry/Cargo.toml b/core/layers/retry/Cargo.toml index 280bfd3eb934..41c25a9aea9d 100644 --- a/core/layers/retry/Cargo.toml +++ b/core/layers/retry/Cargo.toml @@ -32,7 +32,9 @@ version = { workspace = true } all-features = true [dependencies] -backon = { git = "https://github.com/ddupg/backon.git", rev = "bd64facb90cae9983a12f5ec59c71dd2803f6342", features = ["tokio-sleep"] } +backon = { git = "https://github.com/ddupg/backon.git", rev = "bd64facb90cae9983a12f5ec59c71dd2803f6342", features = [ + "tokio-sleep", +] } log = { workspace = true } opendal-core = { path = "../../core", version = "0.58.2", default-features = false } From 250731f9285f634d04a394bd703589966f3d624c Mon Sep 17 00:00:00 2001 From: Xin Sun Date: Thu, 20 Aug 2026 09:59:17 +0800 Subject: [PATCH 6/6] fix(ci): update Ruby BackON lock entry Keep the Ruby workspace lockfile consistent with the retry layer's pinned BackON revision. --- bindings/ruby/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/ruby/Cargo.lock b/bindings/ruby/Cargo.lock index f1d2ca339746..69def0f93e0b 100644 --- a/bindings/ruby/Cargo.lock +++ b/bindings/ruby/Cargo.lock @@ -93,12 +93,12 @@ dependencies = [ [[package]] name = "backon" version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +source = "git+https://github.com/ddupg/backon.git?rev=bd64facb90cae9983a12f5ec59c71dd2803f6342#bd64facb90cae9983a12f5ec59c71dd2803f6342" dependencies = [ "fastrand", "gloo-timers", "tokio", + "web-time", ] [[package]]