org.apache.kafka.common.internals.KafkaFutureImpl Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.kafka.common.internals;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.kafka.common.KafkaFuture;
/**
* A flexible future which supports call chaining and other asynchronous programming patterns.
* This will eventually become a thin shim on top of Java 8's CompletableFuture.
*/
public class KafkaFutureImpl extends KafkaFuture {
/**
* A convenience method that throws the current exception, wrapping it if needed.
*
* In general, KafkaFuture throws CancellationException and InterruptedException directly, and
* wraps all other exceptions in an ExecutionException.
*/
private static void wrapAndThrow(Throwable t) throws InterruptedException, ExecutionException {
if (t instanceof CancellationException) {
throw (CancellationException) t;
} else if (t instanceof InterruptedException) {
throw (InterruptedException) t;
} else {
throw new ExecutionException(t);
}
}
private static class Applicant implements BiConsumer {
private final BaseFunction function;
private final KafkaFutureImpl future;
Applicant(BaseFunction function, KafkaFutureImpl future) {
this.function = function;
this.future = future;
}
@Override
public void accept(A a, Throwable exception) {
if (exception != null) {
future.completeExceptionally(exception);
} else {
try {
B b = function.apply(a);
future.complete(b);
} catch (Throwable t) {
future.completeExceptionally(t);
}
}
}
}
private static class SingleWaiter implements BiConsumer {
private R value = null;
private Throwable exception = null;
private boolean done = false;
@Override
public synchronized void accept(R newValue, Throwable newException) {
this.value = newValue;
this.exception = newException;
this.done = true;
this.notifyAll();
}
synchronized R await() throws InterruptedException, ExecutionException {
while (true) {
if (exception != null)
wrapAndThrow(exception);
if (done)
return value;
this.wait();
}
}
R await(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
long startMs = System.currentTimeMillis();
long waitTimeMs = unit.toMillis(timeout);
long delta = 0;
synchronized (this) {
while (true) {
if (exception != null)
wrapAndThrow(exception);
if (done)
return value;
if (delta >= waitTimeMs) {
throw new TimeoutException();
}
this.wait(waitTimeMs - delta);
delta = System.currentTimeMillis() - startMs;
}
}
}
}
/**
* True if this future is done.
*/
private boolean done = false;
/**
* The value of this future, or null. Protected by the object monitor.
*/
private T value = null;
/**
* The exception associated with this future, or null. Protected by the object monitor.
*/
private Throwable exception = null;
/**
* A list of objects waiting for this future to complete (either successfully or
* exceptionally). Protected by the object monitor.
*/
private List> waiters = new ArrayList<>();
/**
* Returns a new KafkaFuture that, when this future completes normally, is executed with this
* futures's result as the argument to the supplied function.
*/
@Override
public KafkaFuture thenApply(BaseFunction function) {
KafkaFutureImpl future = new KafkaFutureImpl<>();
addWaiter(new Applicant<>(function, future));
return future;
}
public void copyWith(KafkaFuture future, BaseFunction function) {
KafkaFutureImpl futureImpl = (KafkaFutureImpl) future;
futureImpl.addWaiter(new Applicant<>(function, this));
}
/**
* @see KafkaFutureImpl#thenApply(BaseFunction)
*/
@Override
public KafkaFuture thenApply(Function function) {
return thenApply((BaseFunction) function);
}
private static class WhenCompleteBiConsumer implements BiConsumer {
private final KafkaFutureImpl future;
private final BiConsumer super T, ? super Throwable> biConsumer;
WhenCompleteBiConsumer(KafkaFutureImpl future, BiConsumer super T, ? super Throwable> biConsumer) {
this.future = future;
this.biConsumer = biConsumer;
}
@Override
public void accept(T val, Throwable exception) {
try {
if (exception != null) {
biConsumer.accept(null, exception);
} else {
biConsumer.accept(val, null);
}
} catch (Throwable e) {
if (exception == null) {
exception = e;
}
}
if (exception != null) {
future.completeExceptionally(exception);
} else {
future.complete(val);
}
}
}
@Override
public KafkaFuture whenComplete(final BiConsumer super T, ? super Throwable> biConsumer) {
final KafkaFutureImpl future = new KafkaFutureImpl<>();
addWaiter(new WhenCompleteBiConsumer<>(future, biConsumer));
return future;
}
protected synchronized void addWaiter(BiConsumer super T, ? super Throwable> action) {
if (exception != null) {
action.accept(null, exception);
} else if (done) {
action.accept(value, null);
} else {
waiters.add(action);
}
}
@Override
public synchronized boolean complete(T newValue) {
List> oldWaiters;
synchronized (this) {
if (done)
return false;
value = newValue;
done = true;
oldWaiters = waiters;
waiters = null;
}
for (BiConsumer super T, ? super Throwable> waiter : oldWaiters) {
waiter.accept(newValue, null);
}
return true;
}
@Override
public boolean completeExceptionally(Throwable newException) {
List> oldWaiters;
synchronized (this) {
if (done)
return false;
exception = newException;
done = true;
oldWaiters = waiters;
waiters = null;
}
for (BiConsumer super T, ? super Throwable> waiter : oldWaiters) {
waiter.accept(null, newException);
}
return true;
}
/**
* If not already completed, completes this future with a CancellationException. Dependent
* futures that have not already completed will also complete exceptionally, with a
* CompletionException caused by this CancellationException.
*/
@Override
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
return completeExceptionally(new CancellationException()) || exception instanceof CancellationException;
}
/**
* Waits if necessary for this future to complete, and then returns its result.
*/
@Override
public T get() throws InterruptedException, ExecutionException {
SingleWaiter waiter = new SingleWaiter<>();
addWaiter(waiter);
return waiter.await();
}
/**
* Waits if necessary for at most the given time for this future to complete, and then returns
* its result, if available.
*/
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
TimeoutException {
SingleWaiter waiter = new SingleWaiter<>();
addWaiter(waiter);
return waiter.await(timeout, unit);
}
/**
* Returns the result value (or throws any encountered exception) if completed, else returns
* the given valueIfAbsent.
*/
@Override
public synchronized T getNow(T valueIfAbsent) throws InterruptedException, ExecutionException {
if (exception != null)
wrapAndThrow(exception);
if (done)
return value;
return valueIfAbsent;
}
/**
* Returns true if this CompletableFuture was cancelled before it completed normally.
*/
@Override
public synchronized boolean isCancelled() {
return exception instanceof CancellationException;
}
/**
* Returns true if this CompletableFuture completed exceptionally, in any way.
*/
@Override
public synchronized boolean isCompletedExceptionally() {
return exception != null;
}
/**
* Returns true if completed in any fashion: normally, exceptionally, or via cancellation.
*/
@Override
public synchronized boolean isDone() {
return done;
}
@Override
public String toString() {
return String.format("KafkaFuture{value=%s,exception=%s,done=%b}", value, exception, done);
}
}