Skip to content

fix: asset-bundle-budget-null-safe#9399

Open
alejandro-jimenez-dcl wants to merge 2 commits into
devfrom
fix/asset-bundle-budget-null-safe
Open

fix: asset-bundle-budget-null-safe#9399
alejandro-jimenez-dcl wants to merge 2 commits into
devfrom
fix/asset-bundle-budget-null-safe

Conversation

@alejandro-jimenez-dcl

@alejandro-jimenez-dcl alejandro-jimenez-dcl commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Pull Request Description

What does this PR change?

Two small robustness fixes in the streamable loading / web request path:

  • Null-safe budget release in LoadAssetBundleSystem. The pooled StreamableLoadingState can be recycled (its budget disposed and nulled by the pool actionOnRelease) if the promise is forgotten or cancelled while the detached load flow is mid-await. Releasing the budget with !. would throw a NullReferenceException in that window. Switched to ?. to match how the budget is treated everywhere else in the flow (FlowAsync's ongoing-request path, FinalizeLoading, IsWorldInvalid all already null-check it).

  • Honor cancellation token in ArtificialDelayWebRequestController. UniTask.Delay now receives envelope.Ct, so the artificial delay is cancelled together with the request instead of running to completion after the request is already gone.

Test Instructions

Smoke test, nothing in particular. Run the app and confirm scenes/asset bundles load normally with no regressions or new errors in the console.

Quality Checklist

  • Changes have been tested locally
  • Documentation has been updated (if required)
  • Performance impact has been considered
  • For SDK features: Test scene is included

Code Review Reference

Please review our Branch & PR Standards before submitting. It explains the automated review flow, QA/DEV approval requirements, and what each label does — especially useful for first-time contributors.

The pooled StreamableLoadingState can be recycled (budget disposed and
nulled via the pool actionOnRelease) if the promise is forgotten or
cancelled while the detached flow is mid-await. Releasing the budget with
!. would NRE in that window, so guard with ?. to match how the budget is
treated everywhere else in the flow.
Pass envelope.Ct to UniTask.Delay so the artificial delay is cancelled
along with the request instead of running to completion after the request
is already gone.
@alejandro-jimenez-dcl
alejandro-jimenez-dcl requested review from a team as code owners July 17, 2026 09:03
@github-actions
github-actions Bot requested a review from anicalbano July 17, 2026 09:03
@alejandro-jimenez-dcl alejandro-jimenez-dcl changed the title fix/asset-bundle-budget-null-safe fix: asset-bundle-budget-null-safe Jul 17, 2026
@github-actions
github-actions Bot requested review from dalkia and lorux0 July 17, 2026 09:03
@decentraland-bot
decentraland-bot self-requested a review July 17, 2026 09:03
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@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 — fix: asset-bundle-budget-null-safe

STEP 1 — Context & Scope

Files changed: 2 (+4 −2)

  • Explorer/Assets/DCL/Infrastructure/ECS/Unity/StreamableLoading/AssetBundles/LoadAssetBundleSystem.cs
  • Explorer/Assets/DCL/WebRequests/ArtificialDelayWebRequestController.cs

Loaded: CLAUDE.md, LoadSystemBase.cs, StreamableLoadingState.cs, IAcquiredBudget.cs, AcquiredBudget.cs. Searched codebase for all AcquiredBudget usage (20+ files) and all ?.Release() call sites in StreamableLoading/.


STEP 2 — Root-cause check

Change 1 — AcquiredBudget!.Release()AcquiredBudget?.Release()

Problem: AcquiredBudget is declared IAcquiredBudget? (nullable, StreamableLoadingState.cs:74). The old code used the null-forgiving operator !, which suppressed the compiler warning but could NRE at runtime.

Root cause: FlowInternalAsync runs inside a detached async flow (.Forget() at LoadSystemBase.Execute). The StreamableLoadingState is pooled — when the promise is forgotten/cancelled externally, POOL.actionOnRelease calls DisposeBudgetIfExists(), which calls AcquiredBudget.Dispose() (internally calls Release()) then sets AcquiredBudget = null. If the detached flow is mid-await at that point, it would NRE on the !.Release() line.

Fixes cause, not symptom? ✅ Yes. The property IS nullable by declaration and by runtime lifecycle. Using ?. correctly matches the declaration. If null, the budget was already released via Dispose()Release() internally (verified in AcquiredBudget.cs:24-28), so no budget leak occurs. The base class LoadSystemBase.cs:146 already uses state.AcquiredBudget?.Release() for the identical scenario.

Change 2 — Missing cancellationToken on UniTask.Delay

Problem: Without the token, UniTask.Delay runs to completion even after the request is cancelled.

Root cause: The parameter was simply omitted. The method already uses envelope.Ct for GetOptionsAsync() and passes it through to origin.SendAsync(), so the token is available and appropriate.

Fixes cause, not symptom? ✅ Yes — directly addresses the missing cancellation propagation.

STEP 2 VERDICT: PASS


STEP 3 — Design & integration

No new types, systems, managers, or persistent state introduced. Both changes are single-line fixes to existing methods in existing classes.

  • No lifecycle duplication.
  • No per-frame reconciliation added.
  • No new subscriptions, events, or resources.

Owner search: N/A — no new long-lived units.

Teardown/consumption trace: No new subscriptions or resources opened by this diff.

STEP 3 VERDICT: PASS


STEP 4 — Member audit

No public properties or accessors added or changed.


STEP 5 — Line-level review

Pass A (Blocking issues): None found.

  • ✅ No CLAUDE.md violations — ?. on nullable is correct (CLAUDE.md §11 anti-pattern: "Defensive null-checks against non-null declarations" — AcquiredBudget IS nullable, so ?. is appropriate)
  • ✅ No bugs or runtime errors introduced
  • ✅ No security vulnerabilities
  • ✅ No performance issues (both changes are on existing async paths, no new allocations)
  • ✅ No missing error handling
  • ✅ No resource leaks — Release() is idempotent (AcquiredBudget.cs:36-40), and null means already disposed+released
  • ✅ No nullability-contract violations — ?. matches the IAcquiredBudget? declaration

Pass B (Design, encapsulation & resource smells): None found.

  • Comment on line 98-99 is justified — explains why null is possible at this point (pooled state recycling during detached flow), not narrating external behavior.

Parallel review agents (security, architecture, patterns): All returned clean — no findings.


STEP 6 — Complexity assessment

SIMPLE — 2 files, 4 lines changed, straightforward null-safety and cancellation-token fixes. No ECS system changes, no async pattern changes, no architectural modifications.


STEP 7 — QA assessment

QA_REQUIRED: YES — Both files are runtime code in the asset loading pipeline and web request layer. The null-safety fix affects asset bundle loading under cancellation edge cases; the cancellation token fix affects artificial delay behavior during request teardown.


STEP 8 — Non-blocking warnings

None. Main scene not modified.


STEP 9 — Verdict

Both changes are correct, minimal, and aligned with existing codebase patterns. No issues found across manual review and parallel agent analysis (security, architecture, patterns).

REVIEW_RESULT: PASS ✅
COMPLEXITY: SIMPLE
COMPLEXITY_REASON: Null-safety fix in asset bundle loading and cancellation token fix in web request delay — no architectural subsystems affected.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

@github-actions github-actions 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.

Auto-approved based on Jarvis review — simple fix/chore with no blocking issues. QA approval is still required.

@github-actions

Copy link
Copy Markdown
Contributor

Warnings not reduced: 19087 => 19156 — remove at least one warning to merge.

@DafGreco DafGreco left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✔️ PR reviewed and approved by QA on both platforms following instructions playing both happy and un-happy path

Regressions for this ticket had been performed in order to verify that the normal flow is working as expected:

  • [✔️ ] Backpack and wearables in world
  • [✔️ ] Emotes in world and in backpack
  • [ ✔️] Teleport with map/coordinates/Jump In
  • [ ✔️] Chat and multiplayer
  • [✔️ ] Profile card
  • [ ✔️] Camera
  • [ ✔️] Settings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants