Skip to content

fix(components): use identity equality for ComponentOwned lifecycle objects (#6776) - #6777

Open
chiliec wants to merge 1 commit into
flet-dev:mainfrom
chiliec:fix/component-owned-identity-eq
Open

fix(components): use identity equality for ComponentOwned lifecycle objects (#6776)#6777
chiliec wants to merge 1 commit into
flet-dev:mainfrom
chiliec:fix/component-owned-identity-eq

Conversation

@chiliec

@chiliec chiliec commented Aug 17, 2026

Copy link
Copy Markdown

What

Closes #6776.

ComponentOwned and its subclasses Hook and ObservableSubscription are lifecycle objects tracked in lists and matched by identity (in, list.remove). They are dataclasses whose only declared fields are InitVars (owner, observable), so the auto-generated __eq__ compares no fields — making every instance compare equal to every other.

In Component._detach_observable_subscription:

if subscription in self._state.observable_subscriptions:
    subscription.dispose()
    self._state.observable_subscriptions.remove(subscription)

subscription in ... matches a different subscription, and .remove() deletes the wrong one — so the subscription that should have been detached is leaked (the memory/subscription leak in the issue). The same collision affects Hook, which is stored per render slot and compared by identity.

Fix

Set eq=False on ComponentOwned, Hook, and ObservableSubscription so they fall back to object's identity-based __eq__/__hash__. Each subclass is decorated with @dataclass, which regenerates __eq__, so the flag is applied to all three.

Reproduction (before the fix)

s1 = ObservableSubscription(owner, obs_a)
s2 = ObservableSubscription(owner, obs_b)
s1 == s2          # True  ← distinct subscriptions compare equal
s2 in [s1]        # True  ← wrong-match
[s1, s2].remove(s2)  # removes s1 instead of s2

After the fix all three return the correct result (False, False, removes s2).

Tests

Added tests/test_component_owned_identity.py (3 cases): ComponentOwned, Hook, and ObservableSubscription each use identity equality and behave correctly under in/list.remove.

Verified RED→GREEN: reverting the three classes to plain @dataclass fails all three new tests with assert ObservableSubscription() != ObservableSubscription(); with eq=False they pass.

Validation (real results, Python 3.11, pytest)

  • tests/test_component_owned_identity.py: 3/3 pass
  • test_observable.py, test_component_renderer.py, test_component_diff.py, test_component_effects.py: all pass (16/16 together with the new file) — no regressions.

(Some unrelated test modules fail to collect in my environment due to a missing optional msgpack dependency; this happens identically on a clean main checkout and is unrelated to this change.)

Summary by Sourcery

Use identity equality for component-owned lifecycle objects to prevent incorrect list operations and leaked observable subscriptions.

Bug Fixes:

  • Ensure component-owned lifecycle objects use identity equality so detaching subscriptions and managing hooks cannot match or remove a different instance.

Tests:

  • Add regression coverage for identity-based equality and list membership/removal across ComponentOwned, Hook, and ObservableSubscription.

…bjects

ComponentOwned and its subclasses (Hook, ObservableSubscription) are
lifecycle objects tracked in lists and matched with `in`/`list.remove`.
They are dataclasses whose only declared fields are InitVars, so the
generated __eq__ compares no fields and every instance compares equal.

In Component._detach_observable_subscription, `subscription in
self._state.observable_subscriptions` then matches a different
subscription and `.remove()` deletes the wrong one, leaking the
subscription that should have been detached.

Set eq=False on ComponentOwned, Hook and ObservableSubscription so they
fall back to identity equality/hash. Add regression tests.

Closes flet-dev#6776.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@sourcery-ai sourcery-ai 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.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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.

bug: [Bug] Memory leak in Observable subscription due to dataclass __eq__ collision in component._detach_observable_subscription

2 participants