Skip to content

[v0.9] Fix memory leaks and standardize subscription lifecycle in web_core and react renderers #963

@jacobsimionato

Description

@jacobsimionato

This issue tracks critical memory leaks and architectural redundancies identified in the A2UI v0.9 Web, React, and Angular renderers. These issues lead to unbounded memory growth in long-lived surfaces and leaked background processes (e.g., timers) when components are unmounted.

Identified Issues

Core & Shared Logic:

  1. Unbounded Signal Cache in DataModel: The DataModel.signals Map caches every path ever queried. These are only cleared when the entire surface is disposed. In surfaces with many ephemeral components or large lists, this leads to significant memory growth.
  2. FormatString Recursive Leaks: The formatString implementation in basic_functions.ts resolves internal expressions into signals using context.resolveSignal(part) but never calls unsubscribe on them. Any function call (like ${now()}) inside a string will leak its listeners/timers until the DataModel is disposed.
  3. Broken Cleanup Chain in DataContext: resolveSignal in DataContext.ts creates recursive effects for function arguments but does not properly take ownership of unsubscribe methods on signals returned by function implementations.

React Renderer:
4. Redundant Subscription Layers: The React adapter wraps the GenericBinder in additional useSyncExternalStore and useEffect layers. While not a leak, it increases complexity and makes cleanup harder to audit.

Angular Renderer:
5. Root-Scoped Leaks in ComponentBinder: ComponentBinder.ts injects DestroyRef at the root level. All subscriptions created via bind() are tied to the application lifetime rather than the component lifetime, causing massive leaks as users navigate surfaces.
6. Stale Bindings: Angular does not listen to ComponentModel.onUpdated. If component properties change in the protocol (e.g., type remains same but props change), the Angular UI stays stale.

Goals

  • Implement reference counting or explicit signal removal in DataModel.
  • Ensure all recursive signals created by formatString and function calls are tracked and disposed of via a unified unsubscribe chain.
  • Fix the DestroyRef scoping in the Angular renderer.
  • Implement property refresh logic in Angular to match the React GenericBinder behavior.
  • Add exhaustive regression tests verifying that AbortSignal listeners and internal Maps are cleared after component unmount across all frameworks.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    In Progress

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions