Skip to content

Fix retained lifetimes and expand collection coverage - #3446

Merged
dansiegel merged 5 commits into
masterfrom
fix/lifetime-retained-roots
Oct 14, 2026
Merged

dansiegel merged 5 commits into
masterfrom
fix/lifetime-retained-roots

Conversation

@dansiegel

@dansiegel dansiegel commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Description of Change

Clearing an observed child left the former child rooted by its observer node; abandoning a nonvisual region host left its behavior in a static tracker; and a retained module type loader kept a disposed ModuleManager alive through event subscriptions. Release each root and preserve re-created observer/region behavior.

Expand deterministic lifetime coverage for weak/strong event subscriptions, queued callbacks, composite and async commands, journal payloads, factory replacement, region recreation, module disposal and MAUI deferred teardown/ancestor detachment. Tests use non-inlined lifetime helpers, weak references, explicit retained roots and controlled GC/queues. Existing observer cases are not duplicated; merged #3442 DefaultView tests remain included.

Bugs Fixed

  • Release cleared observer descendants and ignore already-captured callbacks after detach, including self-referential chains.
  • Untrack delayed region behaviors even when their weak host has collected; synchronize the static tracker with one shared lock.
  • Remove loader subscriptions before disposal, including previously used loaders replaced in the current list; ignore late callbacks and dispose idempotently.

Related: #3442 (merged lifecycle coverage). The three fixes were reproduced on unchanged production code before implementation; the regression commit is preserved in this branch. Historical #803 and #844 describe different ownership paths and are not claimed as fixed here.

API Changes

None: no public signatures added or changed.

Behavioral Changes

ModuleManager.Run and LoadModule now throw ObjectDisposedException after disposal. Repeated Dispose calls do not dispose loaders again, and callbacks received after disposal do not initialize modules or report progress. Disposal waits for in-flight loader callbacks. When called reentrantly from a callback, it detaches subscriptions immediately and defers loader cleanup until the outer callback exits, allowing loaders to wait for their worker callbacks without holding the callback lock.

Validation

Local suite Result
Core, Release/net10 355 passed
MAUI managed, Release/net10 356 passed
WPF, Release/net472 491 passed
Avalonia managed, Release/net10 435 passed; 41 pre-existing skips
Uno desktop/headless, current-master test setup 451 passed; existing duplicate API-inventory test IDs reported

All new lifetime tests ran as ordinary assertions: no deliberate-failure traits, new skips or failure filters. Builds/tests ran serially using existing SDKs/feeds; Avalonia used its installed build-task DLL via a command-local property.

MAUI root/scope and dispatcher coverage uses mocks and controlled queues. This does not qualify real containers, rendered native windows, native Windows cold activation, Android/iOS/Mac/device lifecycles, or every Prism API. No audit reports, scratch patches, scripts or standalone harness projects are included. Hosted CI will validate this PR's exact head separately.

Review follow-up (bf36a4d): reproduced and fixed reentrant observer detachment and in-flight/reentrant module disposal; isolated the static factory and region fixtures and reset ContainerLocator during teardown. Local validation: Core 356 passed, WPF 494 passed (all seven module cases additionally revalidated after converting the race test to async waits), Avalonia 438 passed / 41 existing skips. The three introduced CS8618 warnings were removed with accurate nullable fixture state in 9a87cab. Uno discovery excludes 28 existing API-inventory overload rows, not lifecycle cases; the TRX verifies all 19 DefaultView lifecycle cases passed. This branch adds no new Uno lifetime-fixture includes.

PR Checklist

  • Has tests (if omitted, state reason in description)
  • Rebased on top of master at time of PR
  • Changes adhere to coding standard

Unchanged master: observer fixture has 2 collection failures and 2 passing controls; WPF lifetime fixtures have 5 failures and 4 passing controls. Covers abandoned tracker, disposed-manager collection, loader replacement, disposal order, late callbacks and nested observer recreation.

# Conflicts:
#	tests/Avalonia/Prism.Avalonia.Tests/Prism.Avalonia.Tests.csproj
#	tests/Wpf/Prism.Wpf.Tests/Prism.Wpf.Tests.csproj
Clear detached observer sources and ignore callbacks already captured before detachment. Untrack delayed region behaviors even when the weak host has died, using a shared tracker lock. Detach module-loader subscriptions before disposing loaders, make disposal idempotent, ignore late callbacks, and reject new work after disposal.

Validation: Core net10 342 passed; WPF net472 targeted lifecycle suites 43 passed; Avalonia net10 40 passed with 3 pre-existing skips. All 23 new lifetime cases passed across those hosts. Uno managed net10 library builds. Baseline failing regressions are preserved in the preceding test commit. No device or native Uno UI qualification.
Carry over weak/strong subscription, queued delivery, composite/async command, journal payload and static factory controls. Keep existing observer regressions once, exercise MAUI scoped teardown through a retained mock root, and serialize dispatcher-sensitive tests with the existing lifecycle collection. Retain current-master DefaultView coverage without duplicating it.

Validation: Core net10 355 passed; MAUI managed net10 356 passed; WPF net472 491 passed; Avalonia net10 435 passed and 41 existing skips; Uno desktop/headless suite 451 passed with existing duplicate API-inventory IDs. No new skips or KnownRetention exclusions. Native/device/real-container paths remain unqualified. Production fix scope is unchanged.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T13:31:11.606634Z bf36a4d New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e83e379a30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Prism.Core/Commands/PropertyObserverNode.cs
Comment thread tests/Prism.Core.Tests/Lifetime/StaticFactoryLifetimeFixture.cs
Comment thread src/Wpf/Prism.Wpf/Modularity/ModuleManager.cs Outdated
Comment thread tests/Prism.Lifetime.Shared/RegionLifetimeFixture.cs
@dansiegel
dansiegel merged commit 65530d2 into master Oct 14, 2026
10 checks passed
@dansiegel
dansiegel deleted the fix/lifetime-retained-roots branch October 14, 2026 16:02
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.

1 participant