Skip to content

fix(misc): scattered null/dispose/cancellation robustness - #9428

Closed
eordano wants to merge 1 commit into
devfrom
chore/clean-core-misc-robustness
Closed

fix(misc): scattered null/dispose/cancellation robustness#9428
eordano wants to merge 1 commit into
devfrom
chore/clean-core-misc-robustness

Conversation

@eordano

@eordano eordano commented Jul 17, 2026

Copy link
Copy Markdown
Member

across auth, map, plugins, init

Production evidence (decentraland Sentry, archive snapshot 2026-07-17):

  • UNITY-TEST-ENVIRONMENT-16E: 60 events / 1 users, last seen 2026-04-25
  • UNITY-TEST-ENVIRONMENT-18V: 5 events / 4 users, last seen 2026-05-14

Supersedes #9414: moved from the fork into the org repo so CI workflows receive repository secrets (fork PRs do not).

…, map, plugins, init

Production evidence (decentraland Sentry, archive snapshot 2026-07-17):
- UNITY-TEST-ENVIRONMENT-16E: 60 events / 1 users, last seen 2026-04-25
- UNITY-TEST-ENVIRONMENT-18V: 5 events / 4 users, last seen 2026-05-14
@eordano
eordano requested review from a team as code owners July 17, 2026 13:36
@decentraland-bot decentraland-bot added the ext-contribution Identifies a contribution which was not initiated by a Unity Developer label Jul 17, 2026
@github-actions
github-actions Bot requested a review from anicalbano July 17, 2026 13:37
@eordano
eordano marked this pull request as draft July 17, 2026 13:43
@eordano

eordano commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

Closed in favor of the compound PR #9430 (team decision — all fixes in this set land and iterate there; each fix remains individually reviewable as its own merge commit in #9430).

@eordano eordano closed this Jul 17, 2026
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

badge

New build in progress, come back later!

@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(misc): scattered null/dispose/cancellation robustness

This PR is closed — superseded by #9430 (compound PR). Findings below should be addressed there.

STEP 2 — Root-cause check

The PR fixes scattered robustness issues backed by Sentry production evidence. Most fixes address root causes:

  • CancellationToken.None → proper tokens (auth views, ChatPlugin, InWorldCameraPlugin, LoadSmartWearablePreviewSceneSystem, InWorldCameraController): Correctly fixes async tasks outliving their owners. PASS.
  • Event unsubscription leaks (ChatPlugin, InWorldCameraPlugin, AnnouncementCreationCardView): Correctly wires teardown at disposal points. PASS.
  • Empty-avatar (0-vertex) skinning pipeline (ComputeShaderSkinning → AvatarCustomSkinningComponent → FixedComputeBufferHandler → AvatarInstantiatorSystem → MakeVertsOutBufferDefragmentationSystem): Comprehensive fix across the full Initialize → Skinning → BufferRent → Defragmentation chain. PASS.

Partial FAIL — AvatarBase.cs: Adds if (AvatarAnimator == null) return; in 12+ methods instead of investigating why the animator is null. If AvatarAnimator can legitimately be null (e.g., during teardown when Unity destroys the component), declare it as Animator? so the compiler enforces null-handling at all call sites (CLAUDE.md §11). If it should never be null, investigate the root cause from the Sentry traces. For void methods, prefer null-conditional (AvatarAnimator?.Method()) over explicit guard-and-return.

STEP 3 — Design & integration

No new systems, plugins, or persistent-state units introduced. New CancellationTokenSource fields in InWorldCameraPlugin and LoadSmartWearablePreviewSceneSystem are properly disposed via SafeCancelAndDispose(). The subscribedClipboardManager capture in AnnouncementCreationCardView correctly decouples subscribe/unsubscribe from ViewDependencies lifetime. PASS.

STEP 4 — Member audit

No new public properties or accessors added. PrivateVoiceChatCallStatusServiceNull.PrivateVoiceChatUpdateReceived changed from field-like to accessor-like event ({ add { } remove { } }) — correct null-object pattern suppressing CS0067. No findings.

STEP 5 — Line-level findings

# Sev File Issue
1 P1 RealUserInAppInitializationFlow.cs:239-243 Null-conditional inconsistency creates NRE path
2 P2 AvatarBase.cs (12 sites) Blanket null-checks on non-nullable field (CLAUDE.md §11)
3 P2 5 files GetInstanceID()GetEntityId() — verify semantic equivalence for Physics.BakeMesh(int instanceID, ...) and identity comparison
4 P2 Multiple files Redundant comments that restate what the code does (CLAUDE.md: default to no comments)

See inline comments for details and suggestions.

Security review: No security issues found. No secrets exposed, no auth changes, no input validation concerns. Exception details logged via ReportHub (internal) are appropriate.

STEP 6 — Complexity

COMPLEX — touches avatar rendering pipeline (GPU skinning/compute buffers), async cancellation patterns, plugin disposal lifecycle, and spans auth, map, camera, chat, voice chat, and initialization subsystems across 41 files.

STEP 7 — QA assessment

YES — changes affect runtime code across auth flow, avatar rendering, map markers, in-world camera, chat, voice chat, scene loading, and user initialization.

STEP 8 — Non-blocking warnings

No Main Scene modification. No additional warnings.

STEP 9 — Verdict

REVIEW_RESULT: FAIL \u274c
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches avatar rendering pipeline (skinning/compute buffers), async cancellation patterns, plugin lifecycle, and map/auth/camera subsystems across 41 files.
QA_REQUIRED: YES


Reviewed by Jarvis \U0001f916 \u00b7 Requested by eordano via Slack

Comment on lines +237 to 243
// is a propagated TaskError.Cancelled, not a genuine auth failure.
if (result.Error?.State != TaskError.Cancelled && !ct.IsCancellationRequested)
{
string message = result.Error.AsMessage();
ReportHub.LogError(ReportCategory.AUTHENTICATION, message);
}
}

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.

[P1] Null-conditional inconsistency → NRE path. result.Error?.State on line 239 uses null-conditional, admitting Error could be null. But line 241 calls result.Error.AsMessage() unconditionally — if Error is null and ct.IsCancellationRequested is false, this throws NRE.

Also, the 2-line comment restates what the conditional already expresses (CLAUDE.md: default to no comments).

Suggested change
// is a propagated TaskError.Cancelled, not a genuine auth failure.
if (result.Error?.State != TaskError.Cancelled && !ct.IsCancellationRequested)
{
string message = result.Error.AsMessage();
ReportHub.LogError(ReportCategory.AUTHENTICATION, message);
}
}
if (!ct.IsCancellationRequested && result.Error is { } error && error.State != TaskError.Cancelled)
ReportHub.LogError(ReportCategory.AUTHENTICATION, error.AsMessage());

Comment on lines +205 to +209
public void SetPointAtLayerWeight(float weight)
{
if (AvatarAnimator == null) return;
AvatarAnimator.SetLayerWeight(rightPointAtLayerIndex, weight);
}

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.

[P2] CLAUDE.md §11: Defensive null-checks against non-nullable declarations. This guard-and-return pattern is repeated in 12+ methods across this file. For void methods, null-conditional (?.) is more concise and idiomatic. The same simplification applies to SetRotationLayerWeight, ResetAnimatorTrigger, SetLayerWeight, and other void methods here.

The broader fix: if AvatarAnimator can legitimately be null (teardown ordering), declare it as Animator? at the field/property site so the compiler enforces handling everywhere.

Suggested change
public void SetPointAtLayerWeight(float weight)
{
if (AvatarAnimator == null) return;
AvatarAnimator.SetLayerWeight(rightPointAtLayerIndex, weight);
}
public void SetPointAtLayerWeight(float weight) =>
AvatarAnimator?.SetLayerWeight(rightPointAtLayerIndex, weight);

Comment on lines +121 to +123
// an empty avatar (no vertices) has nothing to skin
if (VertCount == 0)
return Result.SuccessResult();

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.

[P2] Redundant comment. The comment restates what if (VertCount == 0) return SuccessResult() already says. CLAUDE.md: default to no comments; only add one when the WHY is non-obvious.

Suggested change
// an empty avatar (no vertices) has nothing to skin
if (VertCount == 0)
return Result.SuccessResult();
if (VertCount == 0)
return Result.SuccessResult();

Comment on lines +36 to +40
// A null profile must fail the operation: adding it to the world poisons every
// Profile-querying system with a per-frame NRE (observed when the profiles endpoint
// returns no avatars for the signed-in wallet).
if (profile == null)
throw new System.InvalidOperationException("Self profile could not be resolved (profiles endpoint returned no avatar for the signed-in wallet) — cannot create the player avatar.");

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.

[P2] Redundant comment. The 3-line comment is redundant with the exception message, which already explains the scenario and impact. CLAUDE.md: default to no comments.

Suggested change
// A null profile must fail the operation: adding it to the world poisons every
// Profile-querying system with a per-frame NRE (observed when the profiles endpoint
// returns no avatars for the signed-in wallet).
if (profile == null)
throw new System.InvalidOperationException("Self profile could not be resolved (profiles endpoint returned no avatar for the signed-in wallet) — cannot create the player avatar.");
if (profile == null)
throw new System.InvalidOperationException("Self profile could not be resolved (profiles endpoint returned no avatar for the signed-in wallet) \u2014 cannot create the player avatar.");

Comment on lines +201 to +204
// ENABLE_SDK_OBSERVABLES is a deliberate compile-time toggle; the else branch is kept intact
// so the SDK-observables feature can be disabled by flipping the constant. Suppress the
// unreachable-code warning for the currently-disabled branch without removing it.
#pragma warning disable CS0162

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.

[P2] Over-commented pragma. Three comment lines to explain a standard #pragma warning disable. A short inline comment suffices.

Suggested change
// ENABLE_SDK_OBSERVABLES is a deliberate compile-time toggle; the else branch is kept intact
// so the SDK-observables feature can be disabled by flipping the constant. Suppress the
// unreachable-code warning for the currently-disabled branch without removing it.
#pragma warning disable CS0162
#pragma warning disable CS0162 // compile-time toggle: both branches kept

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

Labels

ext-contribution Identifies a contribution which was not initiated by a Unity Developer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants