Skip to content

1.2.7: replay(1).refCount() keeps latest value in memory even after everyone unsubscribes #5172

@ZuZuK

Description

@ZuZuK

Code sample:

public class MainActivity extends AppCompatActivity {

    private Observable<Object> testObservable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        testObservable = Observable.fromCallable(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                return new VeryBigObject();
            }
        })
        .replay(1)
        .refCount();

        //first subscription - callable and creation of VeryBigObject will be called
        Subscription subscription1 = testObservable.subscribe();
        //second subscription - cached value in OperatorReplay in onNext
        Subscription subscription2 = testObservable.subscribe();
        //first unsubscribe - cached value still in OperatorReplay as we have at least 1 subscriber for refCount
        subscription1.unsubscribe();
        //second unsubscribe - there are no subscribers so my guess is that there should be no cached value in OperatorReplay
        //as no one needs that object
        subscription2.unsubscribe();
        //but after everyone unsubscribe there is a reference to VeryBigObject
    }

    public static class VeryBigObject {
        //it's very big
    }

}

Problem: if I'm storing in field any Observable that contains replay(1).refCount() then this field will store hard reference to that latest value stored in replay buffer even if there are no subscriptions to that replay. This value takes memory and it is not good but maybe it's as-design. Also I think that this bug is actual for any replay(***) of any observables chain that have such operator.

Why am I sad: usually I use such construction to not calculate or get from disc some shared single-instance object so if there are at least one subscriber so that object is calculating on subscribe and other subscribers won't call calculation on subscribe but take already calculated value.

Reference to VeryBigObject

    from `MainActivity` - this (root) object
    from `testObservable (Observable)` - field of MainActivity object stores Observable with replay
    from `onSubscribe (OnSubscribeRefCount)` - field of testObservable stores refCount()
    from `source (OperatorReplay)` - field of OnSubscribeRefCount stores ConnectableObservable of replay()
     from `current (AtomicReference)` - reference to current subscriber, it is only changing to not-null in connect() of OperatorReplay
     from `value (OperatorReplay$ReplaySubscriber)` - value of AtomicReference
     from `buffer (OperatorReplay$SizeBoundReplayBuffer)` - linked list of replay values with limit=1. after everyone unsubscribe it has size=2 index=2
     from `value (OperatorReplay$Node)` - head node of linked list - it's own node value (not from AtomicReference field) is null
     from `value (OperatorReplay$Node)` - next element of linked list from head - it stores VeryBigObject
     from `value (VeryBigObject)` - latest value of replay(1) before everyone unsubscribe

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions