Skip to content

fix(android): release data-binding HybridObject native resources#302

Open
mfazekas wants to merge 3 commits into
mainfrom
fix/android-databinding-hybridobject-lifecycle
Open

fix(android): release data-binding HybridObject native resources#302
mfazekas wants to merge 3 commits into
mainfrom
fix/android-databinding-hybridobject-lifecycle

Conversation

@mfazekas

@mfazekas mfazekas commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Each HybridViewModel*Property / HybridViewModelInstance is a small JS object that pins a JNI global reference plus a native Rive object. Two issues with how those were managed:

dispose() was hidden. A property both extends its generated *Spec (a HybridObject) and delegates BaseHybridViewModelProperty via by. Both declare dispose(), so the delegate's won and hid HybridObject.dispose() (the "hides supertype override" warning). Calling dispose() only cancelled the listener flow and never freed the native part / global ref. Each property now overrides dispose() to do both (removeListeners() + super<*Spec>.dispose()).

memorySize was 0. Hermes schedules GC on JS-heap pressure, so these tiny-looking but native-heavy wrappers were never collected under churn — the JNI global reference table (cap 51200) filled up and the process aborted with JNI ERROR: global reference table overflow. Overriding memorySize gives the GC the true cost so it collects them in time.

Reproducer: added a toggleable Android JNI Global-Ref Overflow page under Reproducers — Start the stress and on an unfixed build the app dies within seconds; with this fix the counter climbs indefinitely. (Same operations as the local harness stress, which aborts every run without the fix and 0 with it.)

mfazekas added 2 commits June 29, 2026 08:29
Each HybridViewModel*Property / HybridViewModelInstance is a small JS object
that pins a JNI global reference plus a native Rive object. Two problems:

- dispose() was hidden: a property both extends its generated *Spec (a
  HybridObject) and delegates BaseHybridViewModelProperty via 'by'; both
  declare dispose(), so the delegate's won and hid HybridObject.dispose().
  Calling dispose() only cancelled the listener flow, never freeing the
  native part / global ref. Each property now overrides dispose() to do both.

- memorySize was 0: Hermes GCs on JS-heap pressure, so it never collected
  these tiny-looking but native-heavy wrappers under churn, and the JNI
  global reference table (cap 51200) overflowed. Report memorySize so the
  GC collects them in time.
Auto-registers under Reproducers. Churns view-model property/instance
accessors (each pins a JNI global ref); without the memorySize fix the
Android global reference table (cap 51200) overflows within seconds, with
it the counter climbs indefinitely.

Copilot AI 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.

Pull request overview

Fixes Android data-binding HybridObject native resource retention by ensuring dispose() frees both listener resources and the underlying Nitro/HybridObject native resources, and by providing Hermes with a realistic memorySize for view-model wrapper objects so GC triggers under native-pressure churn.

Changes:

  • Override dispose() in HybridViewModel*Property wrappers that delegate BaseHybridViewModelProperty so they call both removeListeners() and super<*Spec>.dispose().
  • Add VIEW_MODEL_HYBRID_MEMORY_SIZE and override memorySize on the main view-model wrappers to improve Hermes GC behavior under churn.
  • Add an example reproducer page to stress view-model property/instance accessors and validate the fix against JNI global-ref overflow.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
example/src/reproducers/AndroidGlobalRefOverflow.tsx Adds a manual stress reproducer for Android JNI global-ref overflow under view-model accessor churn.
android/src/main/java/com/margelo/nitro/rive/BaseHybridViewModelProperty.kt Introduces a shared VIEW_MODEL_HYBRID_MEMORY_SIZE constant for Hermes GC pressure hinting.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelTriggerProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelListProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelInstance.kt Sets memorySize for view-model instances to reflect native/JNI cost.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelImageProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt Ensures dispose() frees native resources and sets memorySize.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread example/src/reproducers/AndroidGlobalRefOverflow.tsx
… wrappers

HybridViewModelArtboardProperty and HybridViewModel also pin a JNI global
ref but were left with the default (~0) memorySize, so Hermes could let
them accumulate and overflow the global reference table under churn. Give
them the same VIEW_MODEL_HYBRID_MEMORY_SIZE hint as the other wrappers.
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.

2 participants