All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.client.java.util.OnSubscribeDeferAndWatch Maven / Gradle / Ivy

There is a newer version: 3.7.7
Show newest version
/*
 * Copyright (c) 2016 Couchbase, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.couchbase.client.java.util;

import com.couchbase.client.deps.io.netty.util.ReferenceCounted;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.exceptions.Exceptions;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.observers.Subscribers;
import rx.subjects.AsyncSubject;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Defers the execution of a {@link rx.subjects.Subject} and in addition watches for early unsubscription and
 * cleans up buffers if the content is {@link ReferenceCounted}.
 *
 * Implementation Details:
 *
 * This implementation is very similar to {@link Observable#defer(Func0)} in that it takes a hot observable like
 * a subject and defers the execution of it until someone subscribes. The problem with vanilla defer is that if
 * an early unsubscribe happens (like a downstream timeout firing) the message from the hot observable is not
 * properly consumed anymore which can lead to buffer leaks if it contains a pooled resource.
 *
 * To mitigate this, another subscription is added to the hot observable which checks, at the time of item emission,
 * that the subscription is still present. If it is not the buffers are proactively cleared out, making sure that
 * no trace/leak is left behind.
 *
 *  ♬ Wir hom so vü zum tuan                                ♬ We have so much to do
 *  ♬ Wir hudln und schurdln ummanond                       ♬ We are hurrying and botching around
 *  ♬ Müssen uns sputen des dauert vü zu lang               ♬ We need to hurry, it all takes too long
 *  ♬ Da hüft ka hupen so kummst a ned schneller dran       ♬ No need to honk, you’re not getting served quicker
 *  ♬ Und a ka Fluchen lametier ned gemmas on               ♬ No swearing, no whining, lets get on with it
 *      -- from Skero - "Hudeln"
 *
 * @author Michael Nitschinger
 * @since 2.3.6
 */
public class OnSubscribeDeferAndWatch implements Observable.OnSubscribe {

    /**
     * Defer a hot observable and clean its buffers if needed on early unsubscribe. It currently only works if you
     * are deferring a {@link AsyncSubject}.
     *
     * @param observableFactory the factory of the hot observable.
     * @return a deferred observable which handles cleanup of resources on early unsubscribe.
     */
    public static  Observable deferAndWatch(Func1> observableFactory) {
        return Observable.create(new OnSubscribeDeferAndWatch(observableFactory));
    }

    private final Func1> observableFactory;

    private OnSubscribeDeferAndWatch(Func1> observableFactory) {
        this.observableFactory = observableFactory;
    }

    @Override
    public void call(Subscriber s) {

        // Defer execution of the hot observable.
        Observable o;
        try {
            o = observableFactory.call(s);
        } catch (Throwable t) {
            Exceptions.throwOrReport(t, s);
            return;
        }

        // Hook up the consumer subscription and store it in a reference
        final AtomicReference sr = new AtomicReference();
        final AtomicBoolean emitted = new AtomicBoolean(false);
        sr.set(o.doOnNext(new Action1() {
            @Override
            public void call(T t) {
                emitted.set(true);
            }
        }).unsafeSubscribe(Subscribers.wrap(s)));

        // Add the additional subscription to the hot observable which once an item is emitted
        // will check if the original subscription is still present and if not it will release
        // the buffer.
        o.subscribe(new Subscriber() {
            @Override
            public void onCompleted() {
                // ignored on purpose
            }

            @Override
            public void onError(Throwable e) {
                // ignored on purpose
            }

            @Override
            public void onNext(T t) {
                if (t != null && !emitted.get() && sr.get().isUnsubscribed() && t instanceof ReferenceCounted) {
                    ReferenceCounted rc = (ReferenceCounted) t;
                    if (rc.refCnt() > 0) {
                        rc.release();
                    }
                }
            }
        });
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy