/* * Copyright 2015 Netflix, 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 io.reactivex.netty.channel; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufHolder; import io.netty.util.ReferenceCountUtil; import rx.Observable; import rx.Subscriber; import rx.functions.Action1; import rx.observables.ConnectableObservable; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; /** * Similar to {@link ContentSource} but supports multicast to multiple subscriptions. This source, subscribes upstream * once and then caches the content, till the time {@link #dispose()} is called. * * <h2>Managing {@link ByteBuf} lifecycle.</h2> * * If this source emits {@link ByteBuf} or a {@link ByteBufHolder}, using {@link #autoRelease()} will release the buffer * after emitting it from this source. * * Every subscriber to this source must manage it's own lifecycle of the items it receives i.e. the buffers must be * released by every subscriber post processing. * * <h2>Disposing the source</h2> * * It is mandatory to call {@link #dispose()} on this source when no more subscriptions are required. Failure to do so, * will cause a buffer leak as this source, caches the contents till disposed. * * Typically, {@link #dispose()} can be called as an {@link Subscriber#unsubscribe()} action. * * @param <T> Type of objects emitted by this source. */ public final class DisposableContentSource<T> extends Observable<T> { private final OnSubscribeImpl<T> onSubscribe; private DisposableContentSource(final OnSubscribeImpl<T> onSubscribe) { super(onSubscribe); this.onSubscribe = onSubscribe; } /** * If this source emits {@link ByteBuf} or {@link ByteBufHolder} then using this operator will release the buffer * after it is emitted from this source. * * @return A new instance of the stream with auto-release enabled. */ public Observable<T> autoRelease() { return this.lift(new AutoReleaseOperator<T>()); } /** * Disposes this source. */ public void dispose() { if (onSubscribe.disposed.compareAndSet(false, true)) { for (Object chunk : onSubscribe.chunks) { ReferenceCountUtil.release(chunk); } onSubscribe.chunks.clear(); } } static <X> DisposableContentSource<X> createNew(Observable<X> source) { final ArrayList<X> chunks = new ArrayList<>(); ConnectableObservable<X> replay = source.doOnNext(new Action1<X>() { @Override public void call(X x) { chunks.add(x); } }).replay(); return new DisposableContentSource<>(new OnSubscribeImpl<X>(replay, chunks)); } private static class OnSubscribeImpl<T> implements OnSubscribe<T> { private final ConnectableObservable<T> source; private final ArrayList<T> chunks; private boolean subscribed; private final AtomicBoolean disposed = new AtomicBoolean(); public OnSubscribeImpl(ConnectableObservable<T> source, ArrayList<T> chunks) { this.source = source; this.chunks = chunks; } @Override public void call(Subscriber<? super T> subscriber) { if (disposed.get()) { subscriber.onError(new IllegalStateException("Content source is already disposed.")); } boolean connectNow = false; synchronized (this) { if (!subscribed) { connectNow = true; subscribed = true; } } source.doOnNext(new Action1<T>() { @Override public void call(T msg) { ReferenceCountUtil.retain(msg); } }).unsafeSubscribe(subscriber); if (connectNow) { source.connect(); } } } }