diff --git a/docs/asynchronous-outbound-messaging-design.md b/docs/asynchronous-outbound-messaging-design.md index 652a632a..86e49e78 100644 --- a/docs/asynchronous-outbound-messaging-design.md +++ b/docs/asynchronous-outbound-messaging-design.md @@ -133,6 +133,54 @@ loop { } ``` +#### 3.2.1 Fairness for low-priority frames + +Continuous bursts of urgent messages can prevent the low-priority queue from +ever being drained. To mitigate this without removing the deterministic bias, +each `ConnectionActor` tracks how many high-priority frames have been processed +in a row. After a configurable threshold (`max_high_before_low`), the actor +checks `low_priority_push_rx.try_recv()` and, if a frame is present, processes +it and resets the counter. + +An optional time slice (for example 100 µs) can also be configured. When the +elapsed time spent handling high-priority frames exceeds this slice, and the low +queue is not empty, the actor yields to a low-priority frame. Application +builders expose `with_fairness(FairnessConfig)` where `FairnessConfig` groups +the counter threshold and an optional `time_slice`. The counter defaults to 16 +while `time_slice` is disabled. Setting the counter to zero preserves the +original strict ordering. + +This fairness mechanism ensures low-priority traffic continues to progress even +under sustained high-priority load. + + + +The flow diagram below summarises the fairness logic. + +The diagram shows how the actor yields to the low-priority queue +after N high-priority frames. + + + +```mermaid +flowchart TD + A[Start select! loop] --> B{High-priority frame available?} + B -- Yes --> C[Process high-priority frame] + C --> D[Increment high_priority_counter] + D --> E{high_priority_counter >= max_high_before_low?} + E -- Yes --> F{Low-priority frame available?} + F -- Yes --> G[Process low-priority frame] + G --> H[Reset high_priority_counter] + F -- No --> I[Continue] + E -- No --> I + B -- No --> J{Low-priority frame available?} + J -- Yes --> K[Process low-priority frame] + J -- No --> I + I --> A + H --> A + K --> A +``` + ### 3.3 Connection Actor Overview ```mermaid diff --git a/docs/asynchronous-outbound-messaging-roadmap.md b/docs/asynchronous-outbound-messaging-roadmap.md index e4e30724..0d93d467 100644 --- a/docs/asynchronous-outbound-messaging-roadmap.md +++ b/docs/asynchronous-outbound-messaging-roadmap.md @@ -13,6 +13,8 @@ design documents. - [x] **Connection actor** with a biased `select!` loop that polls for shutdown, high/low queues and response streams as described in [Design §3.2][design-write-loop]. +- [ ] **Fairness counter** to yield to the low-priority queue after bursts of + high-priority frames ([Design §3.2.1][design-fairness]). - [ ] **Run state consolidation** using `Option` receivers and a closed source counter ([Design §3.4][design-actor-state]). - [X] **Internal protocol hooks** `before_send` and `on_command_end` invoked @@ -57,6 +59,7 @@ design documents. [design-actor-state]: asynchronous-outbound-messaging-design.md#34-actor-state-management [design-dlq]: asynchronous-outbound-messaging-design.md#52-optional-dead-letter-queue-dlq-for-critical-messages [design-errors]: asynchronous-outbound-messaging-design.md#5-error-handling--resilience +[design-fairness]: asynchronous-outbound-messaging-design.md#321-fairness-for-low-priority-frames [design-hooks]: asynchronous-outbound-messaging-design.md#43-configuration-via-the-wireframeprotocol-trait [design-push-handle]: asynchronous-outbound-messaging-design.md#41-the-pushhandle [design-queues]: asynchronous-outbound-messaging-design.md#31-prioritised-message-queues diff --git a/docs/multi-layered-testing-strategy.md b/docs/multi-layered-testing-strategy.md index 0c1d66cf..a02e7d08 100644 --- a/docs/multi-layered-testing-strategy.md +++ b/docs/multi-layered-testing-strategy.md @@ -413,7 +413,9 @@ inputs. pushes, low-priority pushes, and multi-frame `Response::Stream`s for a single connection. The test asserts that the final output stream respects the strict priority order (`shutdown > high > low > stream`) and that no frames are ever -lost or reordered within their own channel. +lost or reordered within their own channel. When the fairness counter is +configured, sequences containing continuous high-priority pushes must still +observe periodic low-priority frames. **Measurable Objective:** The test suite must pass **1,000,000 generated test cases**, verifying frame ordering and completeness on every run.