/*
* 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.core.utils;
import com.couchbase.client.core.annotations.InterfaceAudience;
import com.couchbase.client.core.annotations.InterfaceStability;
import rx.Observable;
import rx.Subscriber;
import rx.observables.BlockingObservable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/**
* Contains various utility methods related to blocking operations.
*
* Note that this class has been moved over from "java-client" into "core-io" in the
* 1.3.0 timeframe. On earlier versions, it's counterpart can be found in the "java-client"
* library which depends on "core-io".
*
* @author Michael Nitschinger
* @since 1.3.0
*/
@InterfaceStability.Experimental
@InterfaceAudience.Private
public class Blocking {
/**
* Blocks on an {@link Observable} and returns a single event or throws an {@link Exception}.
*
* Note that when this method is used, only the first item emitted will be returned. The caller needs to make
* sure that the source {@link Observable} only ever returns a single item (or none). The {@link BlockingObservable}
* code applies different operators like single, last, first and more, these need to be applied manually.
*
* This code is based on {@link BlockingObservable#blockForSingle}, but does not wait forever. Instead, it
* utilizes the internal {@link CountDownLatch} to optimize the timeout case, with less GC and CPU overhead
* than chaining in an {@link Observable#timeout(long, TimeUnit)} operator.
*
* If an error happens inside the {@link Observable}, it will be raised as an {@link Exception}. If the timeout
* kicks in, a {@link TimeoutException} nested in a {@link RuntimeException} is thrown to be fully compatible
* with the {@link Observable#timeout(long, TimeUnit)} behavior.
*
* @param observable the source {@link Observable}
* @param timeout the maximum timeout before an exception is thrown.
* @param tu the timeout unit.
* @param <T> the type returned.
* @return the extracted value from the {@link Observable} or throws in an error case.
*/
public static <T> T blockForSingle(final Observable<? extends T> observable, final long timeout,
final TimeUnit tu) {
final CountDownLatch latch = new CountDownLatch(1);
TrackingSubscriber<T> subscriber = new TrackingSubscriber<T>(latch);
observable.subscribe(subscriber);
try {
if (!latch.await(timeout, tu)) {
throw new RuntimeException(new TimeoutException());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while waiting for subscription to complete.", e);
}
if (subscriber.returnException() != null) {
if (subscriber.returnException() instanceof RuntimeException) {
throw (RuntimeException) subscriber.returnException();
} else {
throw new RuntimeException(subscriber.returnException());
}
}
return subscriber.returnItem();
}
/**
* A {@link Subscriber} which tracks the returned item or exception.
*
* By pushing out the {@link Subscriber} in it's own class, the code
* can get rid of {@link AtomicReference} objects and stick with
* plain volatiles instead (since it just needs get/set semantics) and
* reduce allocations a little bit.
*
* @since 1.3.0
*/
private final static class TrackingSubscriber<T> extends Subscriber<T> {
private final CountDownLatch latch;
private volatile T returnItem = null;
private volatile Throwable returnException = null;
public TrackingSubscriber(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError(final Throwable e) {
returnException = e;
latch.countDown();
}
@Override
public void onNext(final T item) {
returnItem = item;
}
public Throwable returnException() {
return returnException;
}
public T returnItem() {
return returnItem;
}
}
}