fix: anr pulse host resolution - #9940
Conversation
🚦 CI StatusWindows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below. Warnings count reduced: 12183 => 12177 All Unity tests passed ✅
|
decentraland-bot
left a comment
There was a problem hiding this comment.
PR Review — #9940 fix: anr pulse host resolution
STEP 2 — Root-cause check: ✅ PASS
The PR correctly identifies and fixes the root cause: Address.SetHost(hostname) calls getaddrinfo synchronously via ENet's native layer, blocking the main thread long enough to trigger the 2.5 s ANR watchdog. The fix pre-resolves the hostname to an IP literal asynchronously via Dns.GetHostAddressesAsync, then passes the literal to SetHost (which skips the resolver). This addresses the cause, not a symptom.
STEP 3 — Design & integration: ✅ PASS
Placement: ResolveIPv4Async is a private method in ENetTransport, which owns the connection lifecycle (ConnectAsync / DisconnectAsync / ForceDisconnectAsync). DNS resolution is an implementation detail of the transport's connect step — correct home.
Owner search: No new long-lived unit is introduced. ResolveIPv4Async is a stateless one-shot async helper within the existing ConnectAsync flow. No persistent state, no subscriptions, no lifecycle to manage.
Teardown / consumption trace: No new subscriptions, event hookups, or persistent resources are added. The Dns.GetHostAddressesAsync task is properly bounded by cancellation (AttachExternalCancellation) and timeout (.Timeout()), so it cannot leak.
Exception integration: PulseHostResolutionException is added to the existing retry filter in ConnectWithRetriesAsync alongside TimeoutException and PulseHandshakeDisconnectedException. The non-retriable terminal path (PulseHandshakeDisconnectedException with !IsRetriable) is unchanged — DNS failures are always retriable. Follows the same pattern as PulseHandshakeDisconnectedException (public, non-sealed, same namespace, Exception base).
STEP 4 — Member audit
| Member | Visibility | Consumers | Verdict |
|---|---|---|---|
ResolveIPv4Async |
private |
1 (ConnectAsync) |
Reasonable extraction — isolates async DNS resolution from the connect flow. Not a merge candidate (§11) since it encapsulates a distinct responsibility with its own error handling. |
PulseHostResolutionException |
public |
2 (thrown in ResolveIPv4Async, caught in ConnectWithRetriesAsync) |
Follows the same pattern as the sibling PulseHandshakeDisconnectedException. Two-constructor API (message-only, with inner exception) matches. |
ConnectAsync param rename ip → hostName |
public |
1 (ConnectInternalAsync) |
Accurate — the parameter now accepts hostnames. Note: ITransport declares it as address; the name divergence is cosmetic (C# doesn't require matching parameter names). |
STEP 5 — Line-level findings
All findings are P2 — see inline comments below. No P0 or P1 issues.
Security review
No security issues found. The hostname originates from urlsSource.Url(DecentralandUrl.Pulse) (trusted internal source). DNS resolution is bounded by timeout and cancellation. Exception messages include the hostname, which is acceptable for internal diagnostics.
STEP 8 — Non-blocking warnings
None. Main scene not modified.
REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Modifies the ENet transport connection path (networking subsystem — DNS resolution, async connect flow, retry error handling)
QA_REQUIRED: YES
Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub
|
🔍 Jarvis reviewed this PR and found no blocking issues, but assessed it as complex — human DEV review is still required before merging. |
…nsport.cs Co-authored-by: Muna <44584806+decentraland-bot@users.noreply.github.com> Signed-off-by: Nicolas Lorusso <56365551+lorux0@users.noreply.github.com>
|
PR #9940, run #33562337985 Overall: ✅ no significant changes Builds: Windows change, Windows baseline, macOS change, macOS baseline How to read this table
Intel Core i5
Apple M1
Exception breakdown
|
|
PR reviewed and approved by QA on both platforms following the PR test instructions. ✅ Build: Test results:
Unrelated errors noted (do not affect verdict):
Verdict: PASS ✅ |
What does this PR change?
ENetTransport.ConnectAsyncpassed a hostname straight toAddress.SetHost, which resolves it with a blockinggetaddrinfo. The method had noawaitbefore that call, so the resolve ran on Unity's main thread and froze the client for as long as the OS resolver took — long enough to trip the 2.5 s ANR watchdog on slow networks.A minidump attached to the ANR issue confirmed it: main thread parked in a wait with
dnsapi,rpcrt4andeneton the stack, 4 s into avatar loading.Now the host is resolved asynchronously in the thread-pool and
SetHostreceives an IP literal, which needs no resolver.This fixes one cause of ANRs during load, not the whole issue — several unrelated causes share the same Sentry group, so it stays open after this merges (referenced without a closing keyword deliberately).
Test Instructions
Steps (standard run):
metaforge explorer run XXXX # ← replace with this PR numberExpected result: multiplayer works as before — other players visible, movement and chat sync.
Quality Checklist