| // Copyright 2024 The Bazel Authors. All rights reserved. |
| // |
| // 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.google.devtools.build.lib.skyframe.serialization; |
| |
| import static com.google.common.util.concurrent.MoreExecutors.directExecutor; |
| import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.util.concurrent.FutureCallback; |
| import com.google.common.util.concurrent.Futures; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.devtools.build.lib.bugreport.BugReporter; |
| import com.google.devtools.build.lib.skyframe.serialization.FingerprintValueStore.MissingFingerprintValueException; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.concurrent.ExecutionException; |
| |
| /** Helpers for serialization futures. */ |
| @VisibleForTesting // package-private |
| public final class FutureHelpers { |
| |
| /** |
| * Waits for {@code future} and returns the result. |
| * |
| * <p>Handles exceptions by converting them into {@link SerializationException}. |
| */ |
| @VisibleForTesting // package-private |
| @CanIgnoreReturnValue // may be called for side effects |
| public static <T> T waitForSerializationFuture(ListenableFuture<T> future) |
| throws SerializationException { |
| try { |
| // TODO: b/297857068 - revisit whether this should handle to interrupts. As of 02/09/24, |
| // serialization doesn't handle interrupts so introducing them here could lead to unforseen |
| // problems. |
| return getUninterruptibly(future); |
| } catch (ExecutionException e) { |
| throw asSerializationException(e.getCause()); |
| } |
| } |
| |
| /** |
| * Gets the done value of {@code future}. |
| * |
| * <p>Handles exceptions by converting them into {@link SerializationException}. |
| */ |
| static <T> T getDoneSerializationFuture(ListenableFuture<T> future) |
| throws SerializationException { |
| try { |
| return Futures.getDone(future); |
| } catch (ExecutionException e) { |
| throw asSerializationException(e.getCause()); |
| } |
| } |
| |
| /** |
| * Waits for {@code future} and returns the result. |
| * |
| * <p>Handles exceptions by converting them into {@link SerializationException}. The {@link |
| * SerializationException} may have a {@link MissingFingerprintValueException} cause. |
| * |
| * <p>The {@link MissingFingerprintValueException} needs special case handling anywhere this |
| * method is used. Outside of test code, this is only possible for via {@link |
| * SharedValueDeserializationContext#deserializeWithSharedValues}, which explicitly handles it. |
| */ |
| @CanIgnoreReturnValue // may be called for side effects |
| static <T> T waitForDeserializationFuture(ListenableFuture<T> future) |
| throws SerializationException { |
| try { |
| // TODO: b/297857068 - revisit whether this should handle to interrupts. As of 02/09/24, |
| // serialization doesn't handle interrupts so introducing them here could lead to unforseen |
| // problems. |
| return getUninterruptibly(future); |
| } catch (ExecutionException e) { |
| throw asDeserializationException(e.getCause()); |
| } |
| } |
| |
| /** |
| * Gets the done value of {@code future}. |
| * |
| * <p>May throw an {@link IllegalStateException} if the future is not done. See {@link |
| * #waitForDeserializationFuture} for a description the {@link SerializationException}. |
| */ |
| static <T> T getDoneDeserializationFuture(ListenableFuture<T> future) |
| throws SerializationException { |
| try { |
| return Futures.getDone(future); |
| } catch (ExecutionException e) { |
| throw asDeserializationException(e.getCause()); |
| } |
| } |
| |
| /** |
| * Reports any errors that occur on {@code combiner}. |
| * |
| * <p>Used when a future value is going to be discarded, but it would be inappropriate to ignore |
| * possible errors. |
| */ |
| static void reportAnyFailures(Futures.FutureCombiner<?> combiner) { |
| Futures.addCallback( |
| combiner.call(() -> null, directExecutor()), |
| new FutureCallback<Void>() { |
| @Override |
| public void onSuccess(Void unused) {} |
| |
| @Override |
| public void onFailure(Throwable t) { |
| BugReporter.defaultInstance().sendBugReport(t); |
| } |
| }, |
| directExecutor()); |
| } |
| |
| /** Combines a list of {@code Void} futures into a single future. */ |
| static ListenableFuture<Void> aggregateStatusFutures(List<ListenableFuture<Void>> futures) { |
| if (futures.size() == 1) { |
| return futures.get(0); |
| } |
| return Futures.whenAllSucceed(futures).call(() -> null, directExecutor()); |
| } |
| |
| private static SerializationException asDeserializationException(Throwable cause) { |
| if (cause instanceof MissingFingerprintValueException) { |
| return new SerializationException(cause); |
| } |
| return asSerializationException(cause); |
| } |
| |
| private static SerializationException asSerializationException(Throwable cause) { |
| if (cause instanceof SerializationException) { |
| return new SerializationException(cause); |
| } |
| if (cause instanceof IOException) { |
| return new SerializationException("serialization I/O error", cause); |
| } |
| return new SerializationException("unexpected serialization error", cause); |
| } |
| |
| private FutureHelpers() {} |
| } |