Skip to content

Add async backpressure API for networking stack#188

Open
okhsunrog wants to merge 1 commit intojamesmunns:mainfrom
okhsunrog:async-backpressure
Open

Add async backpressure API for networking stack#188
okhsunrog wants to merge 1 commit intojamesmunns:mainfrom
okhsunrog:async-backpressure

Conversation

@okhsunrog
Copy link
Copy Markdown
Collaborator

Introduce ProfileBackpressure trait with send_*_with_wait methods that return SendOutcome::Wait when the output queue is full, allowing callers to await and retry instead of dropping messages.

Add async public API methods on NetStack (send_ty_wait, send_raw_wait, etc.) and Topics (broadcast_wait, etc.) that automatically handle the wait-and-retry loop.

Enable embedded toolkits to use backpressure by passing an optional queue handle to Sink::new(). Update all embedded toolkit constructors and demo applications.

Fix broadcast routing to properly return InterfaceFull when all interfaces are full instead of NoRouteToDest.

if any_good {
Ok(())
} else if any_full {
Err(InterfaceSendError::InterfaceFull)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

not sure about the behavior and priority of return values here.

when there are multiple links and the second one fails, the result will be 'Ok' and the 'Full' state of the second or remaining ones is lost.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is actually pre-existing behavior in the non-wait send/send_raw methods (the any_good / any_full priority logic). When at least one edge succeeds, the result is Ok(()) and any InterfaceFull from other edges is silently dropped. The _wait variants inherit the same behavior.

This is arguably correct for broadcasts — they're best-effort ("flood" style). If one edge is congested, the message still reached the other edges and any local sockets. Changing this would require tracking partial delivery state across a retry loop, which adds significant complexity for a case that rarely occurs in practice
(all downstream consumers congested simultaneously).

if any_good {
Ok(())
} else if any_full {
Err(InterfaceSendError::InterfaceFull)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

same here too.

@hydra
Copy link
Copy Markdown
Collaborator

hydra commented Feb 12, 2026

The PR is quite large, I got a bit lost in my very brief look just now, was wondering if there is an example that highlights how the wait behavior can be used. that would go a long way to understanding this PR better rather before I look at the implementation details.

Introduce ProfileBackpressure trait with send_*_with_wait methods that
return SendOutcome::Wait when the output queue is full, allowing callers
to await and retry instead of dropping messages.

Add async public API methods on NetStack (send_ty_wait, send_raw_wait,
etc.) and Topics (broadcast_wait, etc.) that automatically handle the
wait-and-retry loop.

Enable embedded toolkits to use backpressure by passing an optional
queue handle to Sink::new(). Update all embedded toolkit constructors
and demo applications.

Fix broadcast routing to properly return InterfaceFull when all
interfaces are full instead of NoRouteToDest.
@okhsunrog okhsunrog force-pushed the async-backpressure branch from e0b3751 to 0a4973a Compare March 17, 2026 20:10
@okhsunrog
Copy link
Copy Markdown
Collaborator Author

@hydra Added doc comments with usage examples on ProfileBackpressure showing both the non-wait and wait patterns, and how to enable backpressure via the wait_q parameter.
Also added doc comments on InterfaceWait, InterfaceSinkWait, SendOutcome, and all _wait methods on NetStack and Topics.
Also refactored to eliminate code duplication — the routing helpers (broadcast, unicast, unicast_err) are now generic over SendOutcome<W> using W = Infallible for the non-wait path (compiler proves the Wait branch is unreachable). Removed ~180 lines of duplicated _wait variants in inner.rs, extracted broadcast_header helper in
topics.rs.
What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants