com.google.appengine.api.datastore.FutureHelper Maven / Gradle / Ivy
/*
* Copyright 2021 Google LLC
*
* 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
*
* https://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.google.appengine.api.datastore;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.AppEngineInternal;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Utilities for working with {@link Future Futures} in the synchronous datastore api.
*
*/
@AppEngineInternal
public final class FutureHelper {
/**
* Return the result of the provided {@link Future}, converting all checked exceptions to
* unchecked exceptions so the caller doesn't have to handle them. If an {@link ExecutionException
* ExecutionException} is thrown the cause is wrapped in a {@link RuntimeException}. If an {@link
* InterruptedException} is thrown it is wrapped in a {@link DatastoreFailureException}.
*
* @param future The Future whose result we want to return.
* @param The type of the provided Future.
* @return The result of the provided Future.
*/
public static T quietGet(Future future) {
try {
return getInternal(future);
} catch (ExecutionException e) {
throw propagateAsRuntimeException(e);
}
}
/**
* Return the result of the provided {@link Future}, converting all checked exceptions except
* those of the provided type to unchecked exceptions so the caller doesn't have to handle them.
* If an {@link ExecutionException ExecutionException} is thrown and the type of the cause does
* not equal {@code exceptionClass} the cause is wrapped in a {@link RuntimeException}. If the
* type of the cause does equal {@code exceptionClass} the cause itself is thrown. If an {@link
* InterruptedException} is thrown it is wrapped in a {@link DatastoreFailureException}.
*
* @param future The Future whose result we want to return.
* @param The type of the provided Future.
* @param exceptionClass Exceptions of this type will be rethrown.
* @return The result of the provided Future.
* @throws E Thrown If an ExecutionException with a cause of the appropriate type is caught.
*/
public static T quietGet(Future future, Class exceptionClass)
throws E {
try {
return getInternal(future);
} catch (ExecutionException e) {
if (e.getCause().getClass().equals(exceptionClass)) {
@SuppressWarnings("unchecked")
E exception = (E) e.getCause();
throw exception;
}
throw propagateAsRuntimeException(e);
}
}
private static T getInternal(Future future) throws ExecutionException {
try {
return future.get();
} catch (InterruptedException e) {
// RequestManager is known to interrupt the thread when the request times out to give it a
// chance to clean up and shut down.
// According to java.lang.Thread docs, the sole effect of setting interrupt is to cause
// Thread.interrupted() to return true. In particular, it does not cause subsequent entry into
// thread code (sleep, condition, whatever) to have an additional thread interrupt injected.
// That only happens if a thread calls interrupt on another thread that is in a wait of some
// sort.
//
// Generally InterruptedException should be propagated. In this case we propagated the
// exception as part of the DatastoreFailureException. We also set the interrupt bit which
// is a bit dubious.
Thread.currentThread().interrupt();
// We try to give a friendly error message.
if (ApiProxy.getCurrentEnvironment().getRemainingMillis() <= 0) {
throw new DatastoreFailureException(
"The thread has been interrupted and the request has timed out", e);
} else {
throw new DatastoreFailureException("The thread has been interrupted", e);
}
}
}
/**
* Propagates the {@code cause} of the given {@link ExecutionException} as a RuntimeException.
*
* @return nothing will ever be returned; this return type is only for convenience.
*/
private static RuntimeException propagateAsRuntimeException(ExecutionException ee) {
if (ee.getCause() instanceof RuntimeException) {
// unwrap and rethrow
throw (RuntimeException) ee.getCause();
} else if (ee.getCause() instanceof Error) {
// unwrap and rethrow
throw (Error) ee.getCause();
} else {
throw new UndeclaredThrowableException(ee.getCause());
}
}
/**
* A base class for a {@link Future} that derives its result from multiple other futures.
*
* @param The type used by sub-futures.
* @param The type returned by this future.
*/
abstract static class MultiFuture implements Future {
protected final Iterable> futures;
public MultiFuture(Iterable> futures) {
this.futures = futures;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
boolean result = true;
for (Future future : futures) {
result &= future.cancel(mayInterruptIfRunning);
}
return result;
}
@Override
public boolean isCancelled() {
boolean result = true;
for (Future future : futures) {
result &= future.isCancelled();
}
return result;
}
@Override
public boolean isDone() {
boolean result = true;
for (Future future : futures) {
result &= future.isDone();
}
return result;
}
}
/**
* We need a future implementation that clears out the txn callbacks when any variation of get()
* is called. This is important because if get() throws an exception, we don't want that exception
* to resurface when the txn gets committed or rolled back.
*/
static final class TxnAwareFuture implements Future {
private final Future future;
private final Transaction txn;
private final TransactionStack txnStack;
TxnAwareFuture(Future future, Transaction txn, TransactionStack txnStack) {
this.future = future;
this.txn = txn;
this.txnStack = txnStack;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return future.isCancelled();
}
@Override
public boolean isDone() {
return future.isDone();
}
@Override
public T get() throws InterruptedException, ExecutionException {
txnStack.getFutures(txn).remove(future);
return future.get();
}
@Override
public T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
txnStack.getFutures(txn).remove(future);
return future.get(timeout, unit);
}
}
/**
* Wraps an already-resolved result in a {@link Future}.
*
* @param The type of the Future.
*/
static class FakeFuture implements Future {
private final T result;
FakeFuture(T result) {
this.result = result;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// cancel() returns false if the Future has already executed
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return true;
}
@Override
@SuppressWarnings("unused")
public T get() throws ExecutionException {
return result;
}
@Override
public T get(long timeout, TimeUnit unit) {
return result;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy