Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions docs/server/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ server.run().await?;
## Accept loop backoff

The accept loop retries failed `accept()` calls using exponential backoff.
`accept_backoff(initial_delay, max_delay)` sets both bounds in one call. These
values are stored in `BackoffConfig`:
`accept_backoff(cfg)` sets both bounds using a `BackoffConfig` value. The
builder normalises the supplied configuration via `BackoffConfig::normalised`,
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 14, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Use -ize spelling per en-GB-oxendict style.

Adopt -ize spelling in prose while keeping the method name as-is.

- builder normalises the supplied configuration via `BackoffConfig::normalised`,
+ builder normalizes the supplied configuration via `BackoffConfig::normalised`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
builder normalises the supplied configuration via `BackoffConfig::normalised`,
-builder normalises the supplied configuration via `BackoffConfig::normalised`,
+builder normalizes the supplied configuration via `BackoffConfig::normalised`,
🧰 Tools
🪛 LanguageTool

[style] ~24-~24: Would you like to use the Oxford spelling “normalizes”? The spelling ‘normalises’ is also correct.
Context: ...ng a BackoffConfig value. The builder normalises the supplied configuration via `Backoff...

(OXFORD_SPELLING_Z_NOT_S)


[style] ~24-~24: Would you like to use the Oxford spelling “normalized”? The spelling ‘normalised’ is also correct.
Context: ...rmalises the supplied configuration via BackoffConfig::normalised, so out-of-range values are adjusted r...

(OXFORD_SPELLING_Z_NOT_S)

🤖 Prompt for AI Agents
In docs/server/configuration.md around line 24, the prose uses en-GB -ise
spelling ("normalises") but the project prefers -ize; update the sentence to use
"normalizes the supplied configuration via" while leaving the code/method
reference `BackoffConfig::normalised` exactly as-is (do not rename the method),
so the text reads something like: "builder normalizes the supplied configuration
via `BackoffConfig::normalised`".

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@coderabbitai new GitHub issue, please

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

@leynos I'll create a GitHub issue to track the spelling consistency fix.


🏁 Script executed:

#!/bin/bash
gh issue create \
  --title "Fix spelling consistency: use -ize instead of -ise in documentation prose" \
  --body "## Description

Fix spelling inconsistency in \`docs/server/configuration.md\` to follow en-GB-oxendict style (-ize spelling).

## Details

On line 24, change:
- \"builder normalises the supplied configuration via\"
to:
- \"builder normalizes the supplied configuration via\"

The method name \`BackoffConfig::normalised\` should remain unchanged - only update the prose text.

## Context

Identified during PR review: https://github.com/leynos/wireframe/pull/301#discussion_r2277529340

## Reference

- PR: https://github.com/leynos/wireframe/pull/301
- Comment: https://github.com/leynos/wireframe/pull/301#discussion_r2277529340
- Requested by: leynos" \
  --assignee leynos

Length of output: 822


Perfect! I've created GitHub issue #313 to track the spelling consistency fix. The issue documents the need to change "normalises" to "normalizes" in the documentation prose while keeping the method name BackoffConfig::normalised unchanged.

The issue has been assigned to you and includes all the relevant context and references to this PR discussion.

  🐰
 /   \
| o.o |
 \___/
  | |
 /   \

so out-of-range values are adjusted rather than preserved:

- `initial_delay` – starting delay for the first retry, clamped to at least 1
millisecond.
Expand All @@ -37,11 +38,13 @@ values are stored in `BackoffConfig`:
```rust
use std::time::Duration;

use wireframe::{app::WireframeApp, server::WireframeServer};
use wireframe::{app::WireframeApp, server::{WireframeServer, BackoffConfig}};

let cfg = BackoffConfig {
initial_delay: Duration::from_millis(5),
max_delay: Duration::from_millis(500),
};

let server = WireframeServer::new(|| WireframeApp::default())
.accept_backoff(Duration::from_millis(5), Duration::from_millis(500));
.accept_backoff(cfg);
```

`accept_initial_delay` and `accept_max_delay` allow adjusting each parameter
individually.
2 changes: 2 additions & 0 deletions src/server/config/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ where
on_preamble_success: self.on_preamble_success,
on_preamble_failure: self.on_preamble_failure,
ready_tx: self.ready_tx,
backoff_config: self.backoff_config,
state: Bound {
listener: Arc::new(tokio),
},
Expand Down Expand Up @@ -173,6 +174,7 @@ where
on_preamble_success: self.on_preamble_success,
on_preamble_failure: self.on_preamble_failure,
ready_tx: self.ready_tx,
backoff_config: self.backoff_config,
state: Bound {
listener: Arc::new(tokio),
},
Expand Down
20 changes: 19 additions & 1 deletion src/server/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use core::marker::PhantomData;

use tokio::sync::oneshot;

use super::{ServerState, Unbound, WireframeServer};
use super::{BackoffConfig, ServerState, Unbound, WireframeServer};
use crate::{app::WireframeApp, preamble::Preamble};

macro_rules! builder_setter {
Expand Down Expand Up @@ -76,6 +76,7 @@ where
on_preamble_success: None,
on_preamble_failure: None,
ready_tx: None,
backoff_config: BackoffConfig::default(),
state: Unbound,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
_preamble: PhantomData,
}
Expand Down Expand Up @@ -122,6 +123,23 @@ where
ready_signal, ready_tx, tx: oneshot::Sender<()> => Some(tx)
);

builder_setter!(
/// Configure accept-loop backoff behaviour.
///
/// The supplied configuration is passed to
/// [`BackoffConfig::normalised`] (`cfg.normalised()`) before being
/// stored. Normalisation clamps `initial_delay` to at least 1 ms and no
/// greater than `max_delay`. If `initial_delay` exceeds `max_delay`,
/// the values are swapped. Normalisation applies any other adjustments
/// `BackoffConfig::normalised` defines so out-of-range values are
/// corrected rather than preserved.
///
/// Invariants:
/// - `initial_delay` must be >= 1 ms
/// - `initial_delay` must be <= `max_delay`
accept_backoff, backoff_config, cfg: BackoffConfig => cfg.normalised()
);

/// Returns the configured number of worker tasks for the server.
///
/// # Examples
Expand Down
1 change: 1 addition & 0 deletions src/server/config/preamble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ where
on_preamble_success: None,
on_preamble_failure: None,
ready_tx: self.ready_tx,
backoff_config: self.backoff_config,
state: self.state,
_preamble: PhantomData,
}
Expand Down
121 changes: 87 additions & 34 deletions src/server/config/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,24 @@ use std::{
use rstest::rstest;

use super::*;
use crate::server::test_util::{
TestPreamble,
bind_server,
factory,
free_listener,
listener_addr,
server_with_preamble,
};
use bincode::error::DecodeError;
use crate::server::{
test_util::{
TestPreamble,
bind_server,
factory,
free_listener,
listener_addr,
server_with_preamble,
},
BackoffConfig,
use tokio::net::{TcpListener, TcpStream};

Comment on lines +20 to +31
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 14, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix syntax error: close the grouped use import.

The grouped import starting at Line 20 is not closed, causing a compile error. Terminate the block before the next use.

 use crate::server::{
     test_util::{
         TestPreamble,
         bind_server,
         factory,
         free_listener,
         listener_addr,
         server_with_preamble,
     },
-    BackoffConfig,
-    use tokio::net::{TcpListener, TcpStream};
+    BackoffConfig,
+};
+use tokio::net::{TcpListener, TcpStream};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use crate::server::{
test_util::{
TestPreamble,
bind_server,
factory,
free_listener,
listener_addr,
server_with_preamble,
},
BackoffConfig,
use tokio::net::{TcpListener, TcpStream};
use crate::server::{
test_util::{
TestPreamble,
bind_server,
factory,
free_listener,
listener_addr,
server_with_preamble,
},
BackoffConfig,
};
use tokio::net::{TcpListener, TcpStream};
🤖 Prompt for AI Agents
In src/server/config/tests.rs around lines 20 to 31, the grouped use import
beginning with use crate::server::{... is not closed before the next use
statement, causing a syntax error; close the brace and terminate the statement
(add the closing '}' and a semicolon) immediately after server items so the
subsequent use tokio::net::... line stands alone.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@coderabbitai new GitHub issue, please

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

I'll create a GitHub issue for the syntax error in the grouped use import.

@leynos I've created a GitHub issue for the syntax error in the grouped use import. The issue has been assigned to you and includes the details of the problem along with the expected fix.


🏁 Script executed:

#!/bin/bash
gh issue create \
  --title "Fix syntax error in grouped use import in src/server/config/tests.rs" \
  --body "## Problem

There is a syntax error in the grouped use import block in \`src/server/config/tests.rs\` around lines 20-31. The grouped import starting with \`use crate::server::{\` is not properly closed before the next use statement, causing a compile error.

## Location

File: \`src/server/config/tests.rs\`
Lines: 20-31

## Expected Fix

Close the grouped import block with \`};\` before the \`use tokio::net::\` statement:

\`\`\`rust
use crate::server::{
    test_util::{
        TestPreamble,
        bind_server,
        factory,
        free_listener,
        listener_addr,
        server_with_preamble,
    },
    BackoffConfig,
};
use tokio::net::{TcpListener, TcpStream};
\`\`\`

## Backlinks

- PR: https://github.com/leynos/wireframe/pull/301
- Comment: https://github.com/leynos/wireframe/pull/301#discussion_r2277529347
- Requested by: leynos" \
  --assignee leynos

Length of output: 1013


@leynos Perfect! I've successfully created GitHub issue #312 for the syntax error in the grouped use import. The issue has been assigned to you and includes:

  • Clear problem description
  • Specific file location and line numbers
  • The exact fix needed to resolve the compilation error
  • Backlinks to this PR and comment thread

The issue is now ready for you to address when convenient.


🧬 Code Graph Analysis Results
  • File: src/server/config/mod.rs (71-83)
pub fn new(factory: F) -> Self {
        let workers = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get);
        Self {
            factory,
            workers,
            on_preamble_success: None,
            on_preamble_failure: None,
            ready_tx: None,
            backoff_config: BackoffConfig::default(),
            state: Unbound,
            _preamble: PhantomData,
        }
    }
  • File: src/server/config/mod.rs (155-155)
pub const fn worker_count(&self) -> usize { self.workers }
  • File: src/server/config/binding.rs (36-36)
pub const fn local_addr(&self) -> Option<SocketAddr> { None }
  • File: src/server/config/binding.rs (121-121)
pub fn local_addr(&self) -> SocketAddr { self.state.listener.local_addr().ok() }
  • File: src/server/config/binding.rs (56-59)
pub fn bind(self, addr: SocketAddr) -> Result<WireframeServer<F, T, Bound>, ServerError> {
        let std = StdTcpListener::bind(addr).map_err(ServerError::Bind)?;
        self.bind_existing_listener(std)
    }
  • File: src/server/config/binding.rs (143-146)
pub fn bind(self, addr: SocketAddr) -> Result<Self, ServerError> {
        let std = StdTcpListener::bind(addr).map_err(ServerError::Bind)?;
        self.bind_existing_listener(std)
    }
  • File: src/server/test_util.rs (23-25)
pub fn factory() -> impl Fn() -> WireframeApp + Send + Sync + Clone + 'static {
    || WireframeApp::default()
}
  • File: src/server/test_util.rs (32-35)
pub fn free_listener() -> StdTcpListener {
    let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0);
    StdTcpListener::bind(addr).expect("Failed to bind free port listener")
}
  • File: src/server/test_util.rs (74-78)
pub fn listener_addr(listener: &StdTcpListener) -> SocketAddr {
    listener
        .local_addr()
        .expect("failed to get listener address")
}
  • File: src/server/test_util.rs (80-87)
pub fn bind_server<F>(factory: F, listener: StdTcpListener) -> WireframeServer<F, (), Bound>
where
    F: Fn() -> WireframeApp + Send + Sync + Clone + 'static,
{
    WireframeServer::new(factory)
        .bind_existing_listener(listener)
        .expect("Failed to bind")
}
  • File: src/server/test_util.rs (90-95)
pub fn server_with_preamble<F>(factory: F) -> WireframeServer<F, TestPreamble>
where
    F: Fn() -> WireframeApp + Send + Sync + Clone + 'static,
{
    WireframeServer::new(factory).with_preamble::<TestPreamble>()
}
  • File: src/server/runtime.rs (33-33)
fn local_addr(&self) -> io::Result<SocketAddr>;
  • File: src/server/runtime.rs (42-42)
fn local_addr(&self) -> io::Result<SocketAddr> { TcpListener::local_addr(self) }
  • File: src/server/runtime.rs (64-69)
fn default() -> Self {
        Self {
            initial_delay: Duration::from_millis(10),
            max_delay: Duration::from_secs(1),
        }
    }
  • File: tests/preamble.rs (56-58)
WireframeServer::new(factory)
        .workers(1)
        .with_preamble::<HotlinePreamble>
  • File: tests/preamble.rs (132-132)
tokio::sync::oneshot::channel::<HotlinePreamble>
  • File: tests/preamble.rs (133-133)
tokio::sync::oneshot::channel::<()>
  • File: tests/preamble.rs (268-272)
WireframeServer::new(factory.clone())
        .with_preamble::<HotlinePreamble>()
        .on_preamble_decode_success(success_cb::<HotlinePreamble>(hotline_success.clone()))
        .on_preamble_decode_failure(failure_cb(hotline_failure.clone()))
        .with_preamble::<OtherPreamble>

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PreambleHandlerKind {
Success,
Failure,
}

fn expected_default_worker_count() -> usize {
// Mirror the default worker logic to keep tests aligned with `WireframeServer::new`.
Expand Down Expand Up @@ -96,37 +106,61 @@ async fn test_local_addr_after_bind(
}

#[rstest]
#[case("success")]
#[case("failure")]
#[case::success(PreambleHandlerKind::Success)]
#[case::failure(PreambleHandlerKind::Failure)]
#[tokio::test]
async fn test_preamble_handler_registration(
factory: impl Fn() -> WireframeApp + Send + Sync + Clone + 'static,
#[case] handler_type: &str,
#[case] handler: PreambleHandlerKind,
) {
let counter = Arc::new(AtomicUsize::new(0));
let c = counter.clone();

let server = server_with_preamble(factory);
let server = match handler_type {
"success" => server.on_preamble_decode_success(move |_p: &TestPreamble, _| {
let server = match handler {
PreambleHandlerKind::Success => server.on_preamble_decode_success(move |_p: &TestPreamble, _| {
let c = c.clone();
Box::pin(async move {
c.fetch_add(1, Ordering::SeqCst);
Ok(())
})
}),
"failure" => server.on_preamble_decode_failure(move |_err: &DecodeError| {
PreambleHandlerKind::Failure => server.on_preamble_decode_failure(move |_err: &DecodeError| {
c.fetch_add(1, Ordering::SeqCst);
}),
_ => panic!("Invalid handler type"),
};

assert_eq!(counter.load(Ordering::SeqCst), 0);
match handler_type {
"success" => assert!(server.on_preamble_success.is_some()),
"failure" => assert!(server.on_preamble_failure.is_some()),
_ => unreachable!(),
match handler {
Comment thread
leynos marked this conversation as resolved.
PreambleHandlerKind::Success => {
assert!(server.on_preamble_success.is_some());
let handler = server
.on_preamble_success
.as_ref()
.expect("success handler missing");
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("bind listener");
let addr = listener.local_addr().expect("listener addr");
let _client = TcpStream::connect(addr)
.await
.expect("client connect failed");
let (mut stream, _) = listener.accept().await.expect("accept stream");
let preamble = TestPreamble { id: 0, message: String::new() };
handler(&preamble, &mut stream)
.await
.expect("handler failed");
}
PreambleHandlerKind::Failure => {
assert!(server.on_preamble_failure.is_some());
let handler = server
.on_preamble_failure
.as_ref()
.expect("failure handler missing");
handler(&DecodeError::UnexpectedEnd);
}
}
assert_eq!(counter.load(Ordering::SeqCst), 1);
}

#[rstest]
Expand Down Expand Up @@ -204,11 +238,12 @@ async fn test_bind_to_multiple_addresses(
fn test_accept_backoff_configuration(
factory: impl Fn() -> WireframeApp + Send + Sync + Clone + 'static,
) {
let initial = Duration::from_millis(5);
let max = Duration::from_millis(500);
let server = WireframeServer::new(factory).accept_backoff(initial, max);
assert_eq!(server.backoff_config.initial_delay, initial);
assert_eq!(server.backoff_config.max_delay, max);
let cfg = BackoffConfig {
initial_delay: Duration::from_millis(5),
max_delay: Duration::from_millis(500),
};
let server = WireframeServer::new(factory).accept_backoff(cfg);
assert_eq!(server.backoff_config, cfg);
}

/// Behaviour test verifying exponential delay doubling and capping.
Expand Down Expand Up @@ -264,7 +299,8 @@ fn test_accept_initial_delay_configuration(
factory: impl Fn() -> WireframeApp + Send + Sync + Clone + 'static,
) {
let delay = Duration::from_millis(20);
let server = WireframeServer::new(factory).accept_initial_delay(delay);
let cfg = BackoffConfig { initial_delay: delay, ..BackoffConfig::default() };
let server = WireframeServer::new(factory).accept_backoff(cfg);
assert_eq!(server.backoff_config.initial_delay, delay);
}

Expand All @@ -273,21 +309,29 @@ fn test_accept_max_delay_configuration(
factory: impl Fn() -> WireframeApp + Send + Sync + Clone + 'static,
) {
let delay = Duration::from_millis(2000);
let server = WireframeServer::new(factory).accept_max_delay(delay);
let cfg = BackoffConfig { max_delay: delay, ..BackoffConfig::default() };
let server = WireframeServer::new(factory).accept_backoff(cfg);
assert_eq!(server.backoff_config.max_delay, delay);
}

#[rstest]
fn test_backoff_validation(factory: impl Fn() -> WireframeApp + Send + Sync + Clone + 'static) {
let server = WireframeServer::new(factory.clone()).accept_initial_delay(Duration::ZERO);
let server = WireframeServer::new(factory.clone())
.accept_backoff(BackoffConfig { initial_delay: Duration::ZERO, ..BackoffConfig::default() });
assert_eq!(
server.backoff_config.initial_delay,
Duration::from_millis(1)
);

let server = WireframeServer::new(factory)
.accept_initial_delay(Duration::from_millis(100))
.accept_max_delay(Duration::from_millis(50));
.accept_backoff(BackoffConfig {
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_millis(50),
});
assert_eq!(
server.backoff_config.initial_delay,
Duration::from_millis(50)
);
assert_eq!(server.backoff_config.max_delay, Duration::from_millis(100));
}

Expand All @@ -305,24 +349,33 @@ fn test_backoff_default_values(factory: impl Fn() -> WireframeApp + Send + Sync
fn test_initial_delay_exceeds_default_max(
factory: impl Fn() -> WireframeApp + Send + Sync + Clone + 'static,
) {
let server = WireframeServer::new(factory).accept_initial_delay(Duration::from_secs(2));
assert_eq!(server.backoff_config.initial_delay, Duration::from_secs(2));
let cfg = BackoffConfig {
initial_delay: Duration::from_secs(2),
max_delay: Duration::from_secs(1),
};
let server = WireframeServer::new(factory).accept_backoff(cfg);
assert_eq!(server.backoff_config.initial_delay, Duration::from_secs(1));
assert_eq!(server.backoff_config.max_delay, Duration::from_secs(2));
}

#[rstest]
fn test_accept_backoff_parameter_swapping(
factory: impl Fn() -> WireframeApp + Send + Sync + Clone + 'static,
) {
let server = WireframeServer::new(factory.clone())
.accept_backoff(Duration::from_millis(5), Duration::from_millis(1));
let server = WireframeServer::new(factory.clone()).accept_backoff(BackoffConfig {
initial_delay: Duration::from_millis(5),
max_delay: Duration::from_millis(1),
});
assert_eq!(
server.backoff_config.initial_delay,
Duration::from_millis(1)
);
assert_eq!(server.backoff_config.max_delay, Duration::from_millis(5));

let server = WireframeServer::new(factory).accept_backoff(Duration::ZERO, Duration::ZERO);
let server = WireframeServer::new(factory).accept_backoff(BackoffConfig {
initial_delay: Duration::ZERO,
max_delay: Duration::ZERO,
});
assert_eq!(
server.backoff_config.initial_delay,
Duration::from_millis(1)
Expand Down
1 change: 1 addition & 0 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ where
/// Because only one notification may be sent, a new `ready_tx` must be
/// provided each time the server is started.
pub(crate) ready_tx: Option<oneshot::Sender<()>>,
pub(crate) backoff_config: BackoffConfig,
/// Typestate tracking whether the server has been bound to a listener.
/// [`Unbound`] servers require binding before they can run.
pub(crate) state: S,
Expand Down
37 changes: 24 additions & 13 deletions src/server/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl AcceptListener for TcpListener {
/// # Invariants
/// - `initial_delay` must not exceed `max_delay`
/// - `initial_delay` must be at least 1 millisecond
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BackoffConfig {
pub initial_delay: Duration,
pub max_delay: Duration,
Expand All @@ -69,6 +69,18 @@ impl Default for BackoffConfig {
}
}

impl BackoffConfig {
#[must_use]
pub fn normalised(mut self) -> Self {
self.initial_delay = self.initial_delay.max(Duration::from_millis(1));
self.max_delay = self.max_delay.max(Duration::from_millis(1));
if self.initial_delay > self.max_delay {
std::mem::swap(&mut self.initial_delay, &mut self.max_delay);
}
self
}
}
Comment thread
leynos marked this conversation as resolved.

impl<F, T> WireframeServer<F, T, Bound>
where
F: Fn() -> WireframeApp + Send + Sync + Clone + 'static,
Expand Down Expand Up @@ -181,15 +193,9 @@ where
on_preamble_failure,
ready_tx,
state: Bound { listener },
backoff_config,
..
} = self;

if let Some(tx) = ready_tx
&& tx.send(()).is_err()
{
tracing::warn!("Failed to send readiness signal: receiver dropped");
}

let shutdown_token = CancellationToken::new();
let tracker = TaskTracker::new();

Expand All @@ -207,10 +213,15 @@ where
on_failure,
token,
t,
BackoffConfig::default(),
backoff_config,
));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Signal readiness after all workers have been spawned.
if ready_tx.is_some_and(|tx| tx.send(()).is_err()) {
tracing::warn!("Failed to send readiness signal: receiver dropped");
}
Comment thread
leynos marked this conversation as resolved.

select! {
() = shutdown => shutdown_token.cancel(),
() = tracker.wait() => {},
Expand Down Expand Up @@ -284,12 +295,12 @@ pub(super) async fn accept_loop<F, T, L>(
L: AcceptListener + Send + Sync + 'static,
{
debug_assert!(
backoff_config.initial_delay >= Duration::from_millis(1),
"initial_delay must be at least 1ms",
backoff_config.initial_delay <= backoff_config.max_delay,
"BackoffConfig invariant violated: initial_delay > max_delay"
);
debug_assert!(
backoff_config.initial_delay <= backoff_config.max_delay,
"initial_delay must not exceed max_delay",
backoff_config.initial_delay >= Duration::from_millis(1),
"BackoffConfig invariant violated: initial_delay < 1ms"
);
let mut delay = backoff_config.initial_delay;
loop {
Expand Down
Loading