Skip to content

subscribe_events has no unsubscribe API — listener list grows unboundedly across Activity lifecycle #399

Description

@forkwright

Finding

subscribe_events only appends a Box<dyn EventListener> to a Vec. There is no removal API, no capacity cap, and no weak-reference mechanism. Because AndroidEngine outlives any Android Activity, a listener registered on onResume is never dropped — the list grows on every rotation/recreation and is never compacted.

Evidence

crates/akouo-android/src/lib.rs:142-148 — push-only, no remove:

pub fn subscribe_events(&self, listener: Box<dyn EventListener>) {
    let mut guard = self
        .event_listeners
        .lock()
        .unwrap_or_else(|e| e.into_inner());
    guard.push(listener);
}

There is no unsubscribe_events, no handle returned to identify a listener, and no bound on event_listeners.

Why this matters

Each retained listener holds a strong reference to its Kotlin object across the UniFFI trait boundary, preventing garbage collection. After N rotations, N copies of the listener receive every audio event, multiplying callback overhead and keeping event payloads alive longer than intended. Unbounded growth driven by ordinary UI churn is an availability hazard on a constrained sovereign device: a long-running or repeatedly-recreated session accumulates memory and CPU with no ceiling and no operator-visible recovery path short of process restart.

Desired correction

Return an opaque handle (e.g. a u64 id) from subscribe_events, and add unsubscribe_events(id: u64) that removes the matching listener. Alternatively store listeners behind a weak reference so they drop automatically when the Kotlin side is collected. Done when: repeated subscribe-on-onResume across Activity rotations leaves the listener count bounded (a subscribe paired with an unsubscribe returns the count to its prior value).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions