fix: keep re-querying unresolved instances while browsing (#493) - #494
fix: keep re-querying unresolved instances while browsing (#493)#494keepsimple1 wants to merge 2 commits into
Conversation
When a service type has many instances, ServiceFound fires for all of them immediately but many were never resolved: the per-instance Command::Resolve retry chain gives up after max_try (3) tries, and because the instance stayed in `pending_resolves`, add_pending_resolve would never re-arm it. The only remaining resolution path was an unsolicited/browse-elicited cache update, which trickles in about one instance per query cycle on a lossy or IPv6-only network. Instances whose SRV/address answers were lost during those first ~1.5s could stay unresolved indefinitely. Fix: drive follow-up SRV/address queries off the browse retransmission cycle. `query_unresolved_instances` sweeps the PTR cache for the type (minus already-resolved instances) and re-queries each, so a pending instance keeps being queried for as long as the browse is active. This inherits the browse's exponential backoff, so it never stalls yet also never becomes a fixed-interval query storm. Also clear `pending_resolves` when the fast-path Resolve chain ends (resolved or gave up), so the set honestly means "a Resolve chain is in flight" and the fast path can re-arm if new partial records arrive. Adds test_unresolved_instance_not_stranded: a hand-rolled responder announces PTR+SRV with a separate SRV target hostname, withholds the address, and only answers after more than max_try address queries have arrived. Before the fix the daemon goes silent after 3 tries and the instance is never resolved; after the fix the browse cycle keeps querying and it resolves. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the quick turnaround — the diagnosis matches what we saw exactly (resolutions arriving right after a What I can and can't give you: I don't have the failing network. The report came from a downstream user of ours (phunapps/matter-rust#113) whose LAN has ~18 instances of one service type; on mine everything resolves in well under a second, so I can only offer no-regression evidence, not confirmation of the fix. He's away until next week and I've asked him to build against the branch — his run is the one that actually tests it. Tested
|
| unpatched | fix/issue-493 |
|
|---|---|---|
| base-type browse, 15 s | 16 resolved | 16 resolved |
| subtype browse alone, 12 s | 1 resolved | 1 resolved |
| subtype + base concurrently | 1 / 16 | 1 / 16 |
Also exercised through our full stack rather than just a probe: a real Matter controller resolving an operational record and reconnecting to a Thread device, then a 204-attribute read over that session — both fine.
So: no regression observed, and nothing got slower.
Two incidental notes that might be useful:
- I had to bump our requirement from
0.20to0.21for the[patch.crates-io]to apply, so this also exercised 0.21 against our adapter. It compiled and behaved identically — no changes needed on our side for the packet-size or re-exported-error-type changes. - One thing we discovered while working around this, in case it's relevant to the fix's shape: browsing a subtype resolves promptly on the affected network (~266 ms for 3 instances) while the base type stalls, but opening both concurrently re-stalls it — consistent with the bottleneck being per-instance resolution work shared across browses rather than anything per-browse. If your fix makes that distinction disappear, that'd be a good confirmation signal.
Happy to run anything else here, and I'll relay the result from the affected network as soon as I have it.
|
This doesn't seem to fix the issue for me; the services still aren't resolved. Output from the example with this patch: |
|
@qwandor Thanks for running the test! I couldn't pin down what was not working. I've updated the diff to have a better backoff timer for the fast path query. Could you please try it again with the latest diff and also enable debugs? something like: |
|
Sure, here you go, on b255dde: |
|
I dug through @qwandor's debug log against the 0.21.0 source, and I don't think this is a pacing problem — which would explain why the improved backoff didn't move it. What the log showsCounting his run: 6 instances resolved, 17 did not. Every one of the 6 resolved inside the first 350 ms. The run then continued for 63 seconds through browse re-queries at 1 s, 3 s, 7 s, 15 s and 31 s, and produced zero further resolutions. So it isn't "one per cycle" — it's a fast initial batch and then a hard stop. The repeated line for the other 17 is pub fn is_valid(&self) -> bool {
let some_missing = self.ty_domain.is_empty()
|| self.fullname.is_empty()
|| self.host.is_empty()
|| self.addresses.is_empty();
!some_missing
}Those instances have PTR, SRV and TXT — hostname and port are present. What they lack is A/AAAA for the SRV target host. Why they never recover
let max_try = 3;at let instance = dns_ptr.alias();
if !updated_instances.contains(instance) {
continue;
}and Net effect: once an instance is found but its address records don't arrive within the 3-try window, it is stuck for the lifetime of the browse, no matter how many times the PTR is re-queried. That matches the log exactly — 17 stuck, 63 s, 5 re-queries, nothing. It isn't random which ones stallGrouping the instances by fabric prefix, the split is completely stable across the run:
So it's specific hosts whose address records aren't landing, not round-robin starvation across the set. (The SuggestionTrack found-but-address-less instances per active browse and re-issue A/AAAA for their SRV targets on the browse's own re-query schedule, decoupled from Happy to test any diff — and I can reproduce something adjacent here (16 instances, mixed IPv4/IPv6 plus IPv6-only Thread devices), though my link resolves everything promptly, so @qwandor's network remains the real test. For what it's worth downstream: we worked around this by browsing the Matter compressed-fabric subtype rather than the base type, which narrows the query from ~18 instances to 3 and resolves in ~266 ms on his network. That sidesteps the stall rather than fixing it, and it's consistent with the above — fewer instances, so the address queries land inside the retry window. |
Fixes #493
Suspected cause:
Resolution of a found instance is driven by a per-instance
Command::Resolveretry chain that fires every 500 ms but gives up after max_try (3) tries (~1.5 s). The only remaining path to resolution is an unsolicited or browse-elicited cache update.Fix:
Drive follow-up SRV/address queries off the
browseretransmission cycle instead of relying solely on the fast-path chain:Also clear
pending_resolveswhen the fast-path Resolve chain ends (resolved or gave up), so the set honestly means "a Resolve chain is in flight" and the fast path can re-arm if new partial records arrive.