com.couchbase.client.core.utils.UnicastAutoReleaseSubject Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core-io Show documentation
Show all versions of core-io Show documentation
The official Couchbase JVM Core IO Library
/**
* Copyright (c) 2015 Couchbase, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING
* IN THE SOFTWARE.
*/
/**
* 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 com.couchbase.client.core.utils;
import com.couchbase.client.deps.io.netty.util.ReferenceCountUtil;
import rx.Observable;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.internal.operators.BufferUntilSubscriber;
import rx.observers.Subscribers;
import rx.schedulers.Schedulers;
import rx.subjects.Subject;
import rx.subscriptions.Subscriptions;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* This Subject can be used to auto-release reference counted objects when not subscribed before.
*
* The implementation is originally based on a Subject in RxNetty, but adapted to fit the needs of the core. For
* example it does not always do auto releasing which is something that can clash with proper subscribers and as a
* result it never increases the reference count of the ref counted object.
*
* While this subject can also be used with non reference counted objects it doesn't make much sense since they
* can properly be garbage collected by the JVM and do not leak.
*
* @author Nitesh Kant (Netflix)
* @author Michael Nitschinger
* @since 1.1.1
*/
public final class UnicastAutoReleaseSubject extends Subject {
private final State state;
private volatile Observable timeoutScheduler;
private UnicastAutoReleaseSubject(State state) {
super(new OnSubscribeAction(state));
this.state = state;
timeoutScheduler = Observable.empty();
}
private UnicastAutoReleaseSubject(final State state, long noSubscriptionTimeout, TimeUnit timeUnit,
Scheduler scheduler) {
super(new OnSubscribeAction(state));
this.state = state;
timeoutScheduler = Observable.interval(noSubscriptionTimeout, timeUnit, scheduler).take(1);
}
/**
* Creates a new {@link UnicastAutoReleaseSubject} without a no subscription timeout.
* This can cause a memory leak in case no one ever subscribes to this subject. See
* {@link UnicastAutoReleaseSubject} for details.
*
* @param onUnsubscribe An action to be invoked when the sole subscriber to this {@link Subject} unsubscribes.
* @param The type emitted and received by this subject.
*
* @return The new instance of {@link UnicastAutoReleaseSubject}
*/
public static UnicastAutoReleaseSubject createWithoutNoSubscriptionTimeout(Action0 onUnsubscribe) {
State state = new State(onUnsubscribe);
return new UnicastAutoReleaseSubject(state);
}
/**
* Creates a new {@link UnicastAutoReleaseSubject} without a no subscription timeout.
* This can cause a memory leak in case no one ever subscribes to this subject. See
* {@link UnicastAutoReleaseSubject} for details.
*
* @param The type emitted and received by this subject.
*
* @return The new instance of {@link UnicastAutoReleaseSubject}
*/
public static UnicastAutoReleaseSubject createWithoutNoSubscriptionTimeout() {
return createWithoutNoSubscriptionTimeout(null);
}
public static UnicastAutoReleaseSubject create(long noSubscriptionTimeout, TimeUnit timeUnit) {
return create(noSubscriptionTimeout, timeUnit, (Action0)null);
}
public static UnicastAutoReleaseSubject create(long noSubscriptionTimeout, TimeUnit timeUnit,
Action0 onUnsubscribe) {
return create(noSubscriptionTimeout, timeUnit, Schedulers.computation(), onUnsubscribe);
}
public static UnicastAutoReleaseSubject create(long noSubscriptionTimeout, TimeUnit timeUnit,
Scheduler timeoutScheduler) {
return create(noSubscriptionTimeout, timeUnit, timeoutScheduler, null);
}
public static UnicastAutoReleaseSubject create(long noSubscriptionTimeout, TimeUnit timeUnit,
Scheduler timeoutScheduler, Action0 onUnsubscribe) {
State state = new State(onUnsubscribe);
return new UnicastAutoReleaseSubject(state, noSubscriptionTimeout, timeUnit, timeoutScheduler);
}
/**
* This will eagerly dispose this {@link Subject} without waiting for the no subscription timeout period,
* if configured.
*
* This must be invoked when the caller is sure that no one will subscribe to this subject. Any subscriber after
* this call will receive an error that the subject is disposed.
*
* @return {@code true} if the subject was disposed by this call (if and only if there was no subscription).
*/
public boolean disposeIfNotSubscribed() {
if (state.casState(State.STATES.UNSUBSCRIBED, State.STATES.DISPOSED)) {
state.bufferedSubject.lift(new AutoReleaseByteBufOperator()).subscribe(Subscribers.empty()); // Drain all items so that ByteBuf gets released.
return true;
}
return false;
}
/** The common state. */
private static final class State {
/**
* Following are the only possible state transitions:
* UNSUBSCRIBED -> SUBSCRIBED
* UNSUBSCRIBED -> DISPOSED
*/
private enum STATES {
UNSUBSCRIBED /*Initial*/, SUBSCRIBED /*Terminal state*/, DISPOSED/*Terminal state*/
}
/** Field updater for state. */
private static final AtomicIntegerFieldUpdater STATE_UPDATER
= AtomicIntegerFieldUpdater.newUpdater(State.class, "state");
/** Field updater for timeoutScheduled. */
private static final AtomicIntegerFieldUpdater TIMEOUT_SCHEDULED_UPDATER
= AtomicIntegerFieldUpdater.newUpdater(State.class, "timeoutScheduled");
private final Action0 onUnsubscribe;
private final Subject bufferedSubject;
private volatile Subscription timeoutSubscription;
@SuppressWarnings("unused") private volatile int timeoutScheduled; // Boolean
private volatile int state = STATES.UNSUBSCRIBED.ordinal(); /*Values are the ordinals of STATES enum*/
public State(Action0 onUnsubscribe) {
this.onUnsubscribe = onUnsubscribe;
bufferedSubject = BufferUntilSubscriber.create();
}
public boolean casState(STATES expected, STATES next) {
return STATE_UPDATER.compareAndSet(this, expected.ordinal(), next.ordinal());
}
public boolean casTimeoutScheduled() {
return TIMEOUT_SCHEDULED_UPDATER.compareAndSet(this, 0, 1);
}
public void setTimeoutSubscription(Subscription subscription) {
timeoutSubscription = subscription;
}
public void unsubscribeTimeoutSubscription() {
if (timeoutSubscription != null) {
timeoutSubscription.unsubscribe();
}
}
}
private static final class OnSubscribeAction implements OnSubscribe {
private final State state;
public OnSubscribeAction(State state) {
this.state = state;
}
@Override
public void call(final Subscriber subscriber) {
if (state.casState(State.STATES.UNSUBSCRIBED, State.STATES.SUBSCRIBED)) {
subscriber.add(Subscriptions.create(new Action0() {
@Override
public void call() {
if (null != state.onUnsubscribe) {
state.onUnsubscribe.call();
}
}
}));
state.bufferedSubject.subscribe(subscriber);
state.unsubscribeTimeoutSubscription();
} else if(State.STATES.SUBSCRIBED.ordinal() == state.state) {
subscriber.onError(new IllegalStateException("This Observable can only have one subscription. "
+ "Use Observable.publish() if you want to multicast."));
} else if(State.STATES.DISPOSED.ordinal() == state.state) {
subscriber.onError(new IllegalStateException("The Content of this Observable is already released. "
+ "Subscribe earlier or tune the CouchbaseEnvironment#autoreleaseAfter() setting."));
}
}
}
private static class AutoReleaseByteBufOperator implements Operator {
@Override
public Subscriber call(final Subscriber subscriber) {
return new Subscriber() {
@Override
public void onCompleted() {
subscriber.onCompleted();
}
@Override
public void onError(Throwable e) {
subscriber.onError(e);
}
@Override
public void onNext(I t) {
try {
subscriber.onNext(t);
} finally {
ReferenceCountUtil.release(t);
}
}
};
}
}
@Override
public void onCompleted() {
state.bufferedSubject.onCompleted();
}
@Override
public void onError(Throwable e) {
state.bufferedSubject.onError(e);
}
@Override
public void onNext(T t) {
state.bufferedSubject.onNext(t);
// Schedule timeout once and when not subscribed yet.
if (state.casTimeoutScheduled() && state.state == State.STATES.UNSUBSCRIBED.ordinal()) {
state.setTimeoutSubscription(timeoutScheduler.subscribe(new Action1() { // Schedule timeout after the first content arrives.
@Override
public void call(Long aLong) {
disposeIfNotSubscribed();
}
}));
}
}
@Override
public boolean hasObservers() {
return state.state == State.STATES.SUBSCRIBED.ordinal();
}
}