edgeHub: resume store-and-forward promptly after reconnect#7528
Conversation
…p-safe reconnect detection edgeHub's ConnectivityAwareClient ignored the Device SDK connection-status callback (commented out due to spurious alternating Connected/Disconnected callbacks) and relied solely on DeviceConnectivityManager poll timers (disconnected-check 2 min / connected-check 5 min). A short upstream outage (e.g. a parent edgeHub restart) is therefore invisible until the next poll, which turns a ~15s reconnect into a multi-minute store-and-forward stall downstream (observed in the nested-edge RouteMessageL3LeafToL4Module E2E). Re-enable the SDK callback but debounce it: a status change only takes effect after a stable DebounceWindow, and a generation counter cancels superseded timers. This gives immediate detection on a genuine transition while collapsing flap churn (20 alternating raw edges settle to exactly 1 effective transition). Effective-status dedup avoids redundant manager calls; the timer is cancelled on Close/Dispose to prevent post-close callbacks; manager-call failures are observed and logged. Draft/post-LTS: sdkBridgeEnabled + DebounceWindow are currently hardcoded for the E2E experiment and must be made configuration-driven before merge; needs full [Integration] + a real nested-edge run. Adds DebouncedSdkBridgeTest covering flap settling, generation cancellation, close/dispose cancellation, and effective-status dedup.
|
Dug into why the full fix (ce1f114) didn't make 1. The fix's retry path never engaged.
So the 2. The test's success log is misleading (unrelated to this fix, but affects how we read runs). New angle (experiment running): I opened #7529 (draft) to give the nested-E2E edgeHub a persistent (host-mounted) store so it survives the container recreate, and queued it against a plain main binary (no #7528) to isolate the store-wipe hypothesis. If RouteMessage passes with just the persistent store, the failing message was being lost to the store wipe (a test-rig gap, not the reconnect re-pump). If it still stalls, that cleanly isolates the defect to the store-and-forward re-pump in a surviving store, which is what this PR should target. Will post the run result here. |
Summary
Trying -> ConnectedandDisconnected -> Connectedwithout changing the existingDeviceConnectedevent semantics.Why
In the nested E2E failure, a short upstream AMQP disruption becomes a multi-minute store-and-forward delay. Detection is one part of the delay, but the endpoint executor adds another sawtooth: if connectivity returns while the FSM is in
Failing, the queued message waits for the already-armed retry timer.A local reproduction against the real
StoringAsyncEndpointExecutorandEndpointExecutorFsmmeasured:The variance depends on where recovery lands in the 1-to-60-second exponential-backoff cycle. The new recovery signal interrupts that timer. A focused test parks the FSM in a 60-second retry interval and verifies delivery in under 5 seconds after recovery; the real 15-second outage shape also passes the under-5-second assertion.
Validation
Microsoft.Azure.Devices.Routing.Core.Test: 1,159 passedMicrosoft.Azure.Devices.Edge.Hub.CloudProxy.Test: 183 passed, 7 skippedConnectionManagerTest: 25 passedDraft scope
The SDK bridge is enabled for the E2E experiment. Make it configurable before merge.