Skip to content

fix: anr pulse host resolution - #9940

Open
lorux0 wants to merge 2 commits into
devfrom
fix/anr-enet-set-host
Open

fix: anr pulse host resolution#9940
lorux0 wants to merge 2 commits into
devfrom
fix/anr-enet-set-host

Conversation

@lorux0

@lorux0 lorux0 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

What does this PR change?

ENetTransport.ConnectAsync passed a hostname straight to Address.SetHost, which resolves it with a blocking getaddrinfo. The method had no await before 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, rpcrt4 and enet on the stack, 4 s into avatar loading.

Now the host is resolved asynchronously in the thread-pool and SetHost receives 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 number

Expected result: multiplayer works as before — other players visible, movement and chat sync.

Quality Checklist

  • Changes have been tested locally — not verified by the author; needs the DNS-stall scenario above
  • Documentation has been updated (if required) — n/a
  • Performance impact has been considered — one async resolve added to connect; no main-thread blocking work
  • For SDK features: Test scene is included — n/a

@lorux0
lorux0 requested review from a team as code owners September 1, 2026 20:23
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Windows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below.

Name Link
Commit 7b49d75
Logs https://github.com/decentraland/unity-explorer/actions/runs/33558424709
Download Windows https://github.com/decentraland/unity-explorer/suites/90940667570/artifacts/9821660896
Download Windows S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/fix/anr-enet-set-host/pr-25797-7b49d75/Decentraland_windows64.zip
Download Mac https://github.com/decentraland/unity-explorer/suites/90940667570/artifacts/9821560492
Download Mac S3 https://explorer-artifacts.decentraland.org/@dcl/unity-explorer/branch/fix/anr-enet-set-host/pr-25797-7b49d75/Decentraland_macos.zip
Built on 2026-09-01T21:39:45Z

Lint

Warnings count reduced: 12183 => 12177

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 25535 0 13
PlayMode ✅ Passed 248 0 37

@decentraland-bot
decentraland-bot self-requested a review September 1, 2026 20:24

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 iphostName 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

Comment thread Explorer/Assets/DCL/Multiplayer/Connections/Pulse/ENet/ENetTransport.cs Outdated
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🔍 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>
@decentraland-bot

Copy link
Copy Markdown
Contributor

PR #9940, run #33562337985

Overall: ✅ no significant changes

Builds: Windows change, Windows baseline, macOS change, macOS baseline

How to read this table
  • Each build is measured 3 times, interleaved with the other build (change, baseline, change, baseline, ...) in the same session, so both see the same world content and machine state. The values are the median, and (min–max) is the lowest and highest of those runs.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a difference that passed every check: the runs are fully separated (every run of one build faster than every run of the other), and the median difference is at least 3% and at least 0.5 ms.
  • ⚪ within noise — the builds' runs overlap, or the difference is tiny; it cannot be told apart from random variation. Treat it as no change.
  • — informational — the 0.1% worst metrics average only the few worst frames of a run, so a single OS hiccup swings them by a lot; they are shown for context and never earn a verdict.
  • ⚠️ no verdict — the two builds' sessions were not comparable (very different sample counts, or too few usable runs), so no conclusion is drawn from them.
  • Exceptions per run — the average number of exceptions in a run's log, not counting teardown ones logged while the app quits. Flagged only on a difference of at least 2 per run and 1.5× the other build; exception kinds the baseline never threw are called out under the table. The Exception breakdown groups all of them by the explorer's report category and exception type (as totals across the runs).
  • A run that logged unusually many exceptions (at least 10 and 5× the median of its build's runs — e.g. a service was down during it) is excluded from all numbers and called out under the table.
  • The Overall line at the top only reacts to a metric that moved on two or more machines, or by 10% or more on one — a single modest 🟢/🔴 cell can still be a statistical fluke.

Intel Core i5

Metric Baseline Change Δ Result
Samples 4572 (×3) 5706 (×3)
CPU average 19.6 ms (18.3–20.8) 15.7 ms (15.5–19.0) -3.9 ms ⚪ within noise
CPU 1% worst 363.6 ms (341.4–417.0) 248.6 ms (32.7–369.4) -115.1 ms ⚪ within noise
CPU 0.1% worst 453.3 ms (432.8–462.5) 299.3 ms (87.2–431.7) -154.0 ms — informational
GPU average 11.8 ms (11.3–12.8) 10.1 ms (8.8–11.6) -1.7 ms ⚪ within noise
GPU 1% worst 203.2 ms (140.6–208.6) 106.1 ms (21.9–194.0) -97.1 ms ⚪ within noise
GPU 0.1% worst 442.4 ms (432.4–448.3) 289.7 ms (33.1–420.0) -152.6 ms — informational
Exceptions per run 0 0 0 ⚪ no significant change

Apple M1

Metric Baseline Change Δ Result
Samples 3846 (×3) 4056 (×3)
CPU average 23.3 ms (23.0–62.1) 22.0 ms (20.2–23.8) -1.3 ms ⚪ within noise
CPU 1% worst 231.5 ms (230.0–1326.7) 229.1 ms (32.5–232.0) -2.5 ms ⚪ within noise
CPU 0.1% worst 244.0 ms (234.9–15034.0) 233.0 ms (71.5–239.0) -11.1 ms — informational
GPU average 40.2 ms (23.8–41.3) 38.7 ms (37.8–41.8) -1.5 ms ⚪ within noise
GPU 1% worst 48.4 ms (48.4–49.3) 47.4 ms (46.2–50.2) -1.1 ms ⚪ within noise
GPU 0.1% worst 50.9 ms (50.3–57.9) 48.7 ms (47.5–52.6) -2.1 ms — informational
Exceptions per run 0.67 0 -0.67 ⚪ no significant change
Exception breakdown
Exception Baseline (3 runs) Change (3 runs)
[CRDT_ECS_BRIDGE] TimeoutException 1 0
[UNKNOWN] TimeoutException 1 0

@balducciv

Copy link
Copy Markdown

PR reviewed and approved by QA on both platforms following the PR test instructions. ✅
Smoke test performed on macOS and Windows to verify the normal flow is working as expected. ✅

Build: v0.174.0-alpha-fix/anr-enet-set-host-7b49d75 (PR #9940, commit 7b49d75)
OS: macOS (Apple M3 Pro) / Windows 11 (Intel i7-12650H, RTX 4060 Laptop GPU)

Test results:

  • Multiplayer connects normally — other players visible, movement and chat sync (Mac + Windows)
  • Loading stage: Completed confirmed in Player.log on both platforms, no session-health issues

Unrelated errors noted (do not affect verdict):

  • ObjectDisposedException: The CancellationTokenSource has been disposed in SidebarController.Dispose() — occurs after [ExitUtils] Exit requested on both platforms (shutdown-only noise, known baseline pattern, unrelated to this fix)

Verdict: PASS ✅

Player windows 9940.log
Player 9940 mac.log

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.

3 participants