Example usage for com.google.common.util.concurrent Futures catchingAsync

List of usage examples for com.google.common.util.concurrent Futures catchingAsync

Introduction

In this page you can find the example usage for com.google.common.util.concurrent Futures catchingAsync.

Prototype

@GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class")

public static <V, X extends Throwable> ListenableFuture<V> catchingAsync(ListenableFuture<? extends V> input,
        Class<X> exceptionType, AsyncFunction<? super X, ? extends V> fallback) 

Source Link

Document

Returns a Future whose result is taken from the given primary input or, if the primary input fails with the given exceptionType , from the result provided by the fallback .

Usage

From source file:io.v.todos.persistence.syncbase.SyncbaseMain.java

private ListenableFuture<SyncgroupSpec> joinWithRetry(final Id listId, final int numTimes, final int limit) {
    final String debugString = (numTimes + 1) + "/" + limit + " for: " + listId;
    Log.d(TAG, "Join attempt " + debugString);
    if (numTimes + 1 == limit) { // final attempt!
        return joinListSyncgroup(listId);
    }/*from w w  w . j  a  v  a  2 s  . co m*/
    // Note: This can be easily converted to exponential backoff.
    final long startTime = System.currentTimeMillis();
    return Futures.catchingAsync(joinListSyncgroup(listId), SyncgroupJoinFailedException.class,
            new AsyncFunction<SyncgroupJoinFailedException, SyncgroupSpec>() {
                public ListenableFuture<SyncgroupSpec> apply(@Nullable SyncgroupJoinFailedException input) {
                    long failTime = System.currentTimeMillis();
                    long delay = Math.max(0, MIN_RETRY_DELAY + startTime - failTime);

                    Log.d(TAG, "Join failed. Sleeping " + debugString + " with delay " + delay);
                    return sExecutor.schedule(new Callable<SyncgroupSpec>() {

                        @Override
                        public SyncgroupSpec call() {
                            Log.d(TAG, "Sleep done. Retry " + debugString);

                            // If this errors, then we will not get another chance to
                            // see this syncgroup until the app is restarted.
                            try {
                                return joinWithRetry(listId, numTimes + 1, limit).get();
                            } catch (InterruptedException | ExecutionException e) {
                                return null;
                            }
                        }
                    }, delay, TimeUnit.MILLISECONDS);
                }
            });
}

From source file:io.vitess.client.grpc.GrpcClient.java

@Override
public ListenableFuture<BeginResponse> begin(Context ctx, BeginRequest request) throws SQLException {
    return Futures.catchingAsync(getFutureStub(ctx).begin(request), Exception.class,
            new ExceptionConverter<BeginResponse>());
}

From source file:io.vitess.client.grpc.GrpcClient.java

@Override
public ListenableFuture<CommitResponse> commit(Context ctx, CommitRequest request) throws SQLException {
    return Futures.catchingAsync(getFutureStub(ctx).commit(request), Exception.class,
            new ExceptionConverter<CommitResponse>());
}

From source file:io.vitess.client.grpc.GrpcClient.java

@Override
public ListenableFuture<RollbackResponse> rollback(Context ctx, RollbackRequest request) throws SQLException {
    return Futures.catchingAsync(getFutureStub(ctx).rollback(request), Exception.class,
            new ExceptionConverter<RollbackResponse>());
}

From source file:io.vitess.client.grpc.GrpcClient.java

@Override
public ListenableFuture<SplitQueryResponse> splitQuery(Context ctx, SplitQueryRequest request)
        throws SQLException {
    return Futures.catchingAsync(getFutureStub(ctx).splitQuery(request), Exception.class,
            new ExceptionConverter<SplitQueryResponse>());
}

From source file:io.vitess.client.grpc.GrpcClient.java

@Override
public ListenableFuture<GetSrvKeyspaceResponse> getSrvKeyspace(Context ctx, GetSrvKeyspaceRequest request)
        throws SQLException {
    return Futures.catchingAsync(getFutureStub(ctx).getSrvKeyspace(request), Exception.class,
            new ExceptionConverter<GetSrvKeyspaceResponse>());
}

From source file:com.facebook.buck.rules.CachingBuildRuleBuilder.java

ListenableFuture<BuildResult> build() {
    final AtomicReference<Long> outputSize = Atomics.newReference();

    ListenableFuture<List<BuildResult>> depResults = Futures.immediateFuture(Collections.emptyList());

    // If we're performing a deep build, guarantee that all dependencies will *always* get
    // materialized locally
    if (buildMode == CachingBuildEngine.BuildMode.DEEP
            || buildMode == CachingBuildEngine.BuildMode.POPULATE_FROM_REMOTE_CACHE) {
        depResults = buildRuleBuilderDelegate.getDepResults(rule, buildContext, executionContext);
    }//w  ww  .  ja  v  a 2 s. c o m

    ListenableFuture<BuildResult> buildResult = Futures.transformAsync(depResults,
            input -> buildOrFetchFromCache(),
            serviceByAdjustingDefaultWeightsTo(CachingBuildEngine.SCHEDULING_MORE_WORK_RESOURCE_AMOUNTS));

    // Check immediately (without posting a new task) for a failure so that we can short-circuit
    // pending work. Use .catchingAsync() instead of .catching() so that we can propagate unchecked
    // exceptions.
    buildResult = Futures.catchingAsync(buildResult, Throwable.class, throwable -> {
        Preconditions.checkNotNull(throwable);
        buildRuleBuilderDelegate.setFirstFailure(throwable);
        Throwables.throwIfInstanceOf(throwable, Exception.class);
        throw new RuntimeException(throwable);
    });

    buildResult = Futures.transform(buildResult, (result) -> {
        buildRuleBuilderDelegate.markRuleAsUsed(rule, buildContext.getEventBus());
        return result;
    }, MoreExecutors.directExecutor());

    // Setup a callback to handle either the cached or built locally cases.
    AsyncFunction<BuildResult, BuildResult> callback = input -> {

        // If we weren't successful, exit now.
        if (input.getStatus() != BuildRuleStatus.SUCCESS) {
            return Futures.immediateFuture(input);
        }

        try (Scope scope = LeafEvents.scope(buildContext.getEventBus(), "finalizing_build_rule")) {
            // We shouldn't see any build fail result at this point.
            BuildRuleSuccessType success = Preconditions.checkNotNull(input.getSuccess());

            // If we didn't build the rule locally, reload the recorded paths from the build
            // metadata.
            if (success != BuildRuleSuccessType.BUILT_LOCALLY) {
                try {
                    for (String str : onDiskBuildInfo.getValuesOrThrow(BuildInfo.MetadataKey.RECORDED_PATHS)) {
                        buildInfoRecorder.recordArtifact(Paths.get(str));
                    }
                } catch (IOException e) {
                    LOG.error(e, "Failed to read RECORDED_PATHS for %s", rule);
                    throw e;
                }
            }

            // Try get the output size now that all outputs have been recorded.
            if (success == BuildRuleSuccessType.BUILT_LOCALLY) {
                outputSize.set(buildInfoRecorder.getOutputSize());
            }

            // If the success type means the rule has potentially changed it's outputs...
            if (success.outputsHaveChanged()) {

                // The build has succeeded, whether we've fetched from cache, or built locally.
                // So run the post-build steps.
                if (rule instanceof HasPostBuildSteps) {
                    executePostBuildSteps(
                            ((HasPostBuildSteps) rule).getPostBuildSteps(buildContext.getBuildContext()));
                }

                // Invalidate any cached hashes for the output paths, since we've updated them.
                for (Path path : buildInfoRecorder.getRecordedPaths()) {
                    fileHashCache.invalidate(rule.getProjectFilesystem().resolve(path));
                }
            }

            if (SupportsInputBasedRuleKey.isSupported(rule) && success == BuildRuleSuccessType.BUILT_LOCALLY
                    && !buildInfoRecorder.getBuildMetadataFor(BuildInfo.MetadataKey.INPUT_BASED_RULE_KEY)
                            .isPresent()) {
                // Doing this here is probably not strictly necessary, however in the case of
                // pipelined rules built locally we will never do an input-based cache check.
                // That check would have written the key to metadata, and there are some asserts
                // during cache upload that try to ensure they are present.
                Optional<RuleKey> inputRuleKey = calculateInputBasedRuleKey(buildContext.getEventBus());
                if (inputRuleKey.isPresent()) {
                    buildInfoRecorder.addBuildMetadata(BuildInfo.MetadataKey.INPUT_BASED_RULE_KEY,
                            inputRuleKey.get().toString());
                }
            }

            // If this rule uses dep files and we built locally, make sure we store the new dep file
            // list and re-calculate the dep file rule key.
            if (useDependencyFileRuleKey() && success == BuildRuleSuccessType.BUILT_LOCALLY) {

                // Query the rule for the actual inputs it used.
                ImmutableList<SourcePath> inputs = ((SupportsDependencyFileRuleKey) rule)
                        .getInputsAfterBuildingLocally(buildContext.getBuildContext(),
                                executionContext.getCellPathResolver());

                // Record the inputs into our metadata for next time.
                // TODO(#9117006): We don't support a way to serlialize `SourcePath`s to the cache,
                // so need to use DependencyFileEntry's instead and recover them on deserialization.
                ImmutableList<String> inputStrings = inputs.stream()
                        .map(inputString -> DependencyFileEntry.fromSourcePath(inputString, pathResolver))
                        .map(MoreFunctions.toJsonFunction()).collect(MoreCollectors.toImmutableList());
                buildInfoRecorder.addMetadata(BuildInfo.MetadataKey.DEP_FILE, inputStrings);

                // Re-calculate and store the depfile rule key for next time.
                Optional<RuleKeyAndInputs> depFileRuleKeyAndInputs = calculateDepFileRuleKey(
                        Optional.of(inputStrings), /* allowMissingInputs */ false);
                if (depFileRuleKeyAndInputs.isPresent()) {
                    RuleKey depFileRuleKey = depFileRuleKeyAndInputs.get().getRuleKey();
                    buildInfoRecorder.addBuildMetadata(BuildInfo.MetadataKey.DEP_FILE_RULE_KEY,
                            depFileRuleKey.toString());

                    // Push an updated manifest to the cache.
                    if (useManifestCaching()) {
                        Optional<RuleKeyAndInputs> manifestKey = calculateManifestKey(
                                buildContext.getEventBus());
                        if (manifestKey.isPresent()) {
                            buildInfoRecorder.addBuildMetadata(BuildInfo.MetadataKey.MANIFEST_KEY,
                                    manifestKey.get().getRuleKey().toString());
                            updateAndStoreManifest(depFileRuleKeyAndInputs.get().getRuleKey(),
                                    depFileRuleKeyAndInputs.get().getInputs(), manifestKey.get(),
                                    buildContext.getArtifactCache());
                        }
                    }
                }
            }

            // If this rule was built locally, grab and record the output hashes in the build
            // metadata so that cache hits avoid re-hashing file contents.  Since we use output
            // hashes for input-based rule keys and for detecting non-determinism, we would spend
            // a lot of time re-hashing output paths -- potentially in serialized in a single step.
            // So, do the hashing here to distribute the workload across several threads and cache
            // the results.
            //
            // Also, since hashing outputs can potentially be expensive, we avoid doing this for
            // rules that are marked as uncacheable.  The rationale here is that they are likely not
            // cached due to the sheer size which would be costly to hash or builtin non-determinism
            // in the rule which somewhat defeats the purpose of logging the hash.
            if (success == BuildRuleSuccessType.BUILT_LOCALLY
                    && shouldUploadToCache(success, Preconditions.checkNotNull(outputSize.get()))) {
                ImmutableSortedMap.Builder<String, String> outputHashes = ImmutableSortedMap.naturalOrder();
                for (Path path : buildInfoRecorder.getOutputPaths()) {
                    outputHashes.put(path.toString(),
                            fileHashCache.get(rule.getProjectFilesystem().resolve(path)).toString());
                }
                buildInfoRecorder.addBuildMetadata(BuildInfo.MetadataKey.RECORDED_PATH_HASHES,
                        outputHashes.build());
            }

            // If this rule was fetched from cache, seed the file hash cache with the recorded
            // output hashes from the build metadata.  Since outputs which have been changed have
            // already been invalidated above, this is purely a best-effort optimization -- if the
            // the output hashes weren't recorded in the cache we do nothing.
            if (success != BuildRuleSuccessType.BUILT_LOCALLY && success.outputsHaveChanged()) {
                Optional<ImmutableMap<String, String>> hashes = onDiskBuildInfo
                        .getBuildMap(BuildInfo.MetadataKey.RECORDED_PATH_HASHES);

                // We only seed after first verifying the recorded path hashes.  This prevents the
                // optimization, but is useful to keep in place for a while to verify this optimization
                // is causing issues.
                if (hashes.isPresent() && verifyRecordedPathHashes(rule.getBuildTarget(),
                        rule.getProjectFilesystem(), hashes.get())) {

                    // Seed the cache with the hashes.
                    for (Map.Entry<String, String> ent : hashes.get().entrySet()) {
                        Path path = rule.getProjectFilesystem().getPath(ent.getKey());
                        HashCode hashCode = HashCode.fromString(ent.getValue());
                        fileHashCache.set(rule.getProjectFilesystem().resolve(path), hashCode);
                    }
                }
            }

            // Make sure the origin field is filled in.
            BuildId buildId = buildContext.getBuildId();
            if (success == BuildRuleSuccessType.BUILT_LOCALLY) {
                buildInfoRecorder.addBuildMetadata(BuildInfo.MetadataKey.ORIGIN_BUILD_ID, buildId.toString());
            } else if (success.outputsHaveChanged()) {
                Preconditions.checkState(
                        buildInfoRecorder.getBuildMetadataFor(BuildInfo.MetadataKey.ORIGIN_BUILD_ID)
                                .isPresent(),
                        "Cache hits must populate the %s field (%s)", BuildInfo.MetadataKey.ORIGIN_BUILD_ID,
                        success);
            }

            // Make sure that all of the local files have the same values they would as if the
            // rule had been built locally.
            buildInfoRecorder.addBuildMetadata(BuildInfo.MetadataKey.TARGET, rule.getBuildTarget().toString());
            buildInfoRecorder.addMetadata(BuildInfo.MetadataKey.RECORDED_PATHS,
                    buildInfoRecorder.getRecordedPaths().stream().map(Object::toString)
                            .collect(MoreCollectors.toImmutableList()));
            if (success.shouldWriteRecordedMetadataToDiskAfterBuilding()) {
                try {
                    boolean clearExistingMetadata = success.shouldClearAndOverwriteMetadataOnDisk();
                    buildInfoRecorder.writeMetadataToDisk(clearExistingMetadata);
                } catch (IOException e) {
                    throw new IOException(String.format("Failed to write metadata to disk for %s.", rule), e);
                }
            }

            // Give the rule a chance to populate its internal data structures now that all of
            // the files should be in a valid state.
            try {
                if (rule instanceof InitializableFromDisk) {
                    doInitializeFromDisk((InitializableFromDisk<?>) rule);
                }
            } catch (IOException e) {
                throw new IOException(
                        String.format("Error initializing %s from disk: %s.", rule, e.getMessage()), e);
            }
        }

        return Futures.immediateFuture(input);
    };
    buildResult = Futures.transformAsync(buildResult, ruleAsyncFunction(buildContext.getEventBus(), callback),
            serviceByAdjustingDefaultWeightsTo(CachingBuildEngine.RULE_KEY_COMPUTATION_RESOURCE_AMOUNTS));

    buildResult = Futures.catchingAsync(buildResult, Throwable.class, thrown -> {
        LOG.debug(thrown, "Building rule [%s] failed.", rule.getBuildTarget());

        if (consoleLogBuildFailuresInline) {
            buildContext.getEventBus().post(ConsoleEvent.severe(getErrorMessageIncludingBuildRule(thrown)));
        }

        thrown = maybeAttachBuildRuleNameToException(thrown);
        recordFailureAndCleanUp(thrown);

        return Futures.immediateFuture(BuildResult.failure(rule, thrown));
    });

    // Do things that need to happen after either success or failure, but don't block the dependents
    // while doing so:
    buildRuleBuilderDelegate
            .addAsyncCallback(MoreFutures.addListenableCallback(buildResult, new FutureCallback<BuildResult>() {

                private void uploadToCache(BuildRuleSuccessType success) {

                    // Collect up all the rule keys we have index the artifact in the cache with.
                    Set<RuleKey> ruleKeys = new HashSet<>();

                    // If the rule key has changed (and is not already in the cache), we need to push
                    // the artifact to cache using the new key.
                    ruleKeys.add(ruleKeyFactories.getDefaultRuleKeyFactory().build(rule));

                    // If the input-based rule key has changed, we need to push the artifact to cache
                    // using the new key.
                    if (SupportsInputBasedRuleKey.isSupported(rule)) {
                        Optional<RuleKey> calculatedRuleKey = calculateInputBasedRuleKey(
                                buildContext.getEventBus());
                        Optional<RuleKey> onDiskRuleKey = onDiskBuildInfo
                                .getRuleKey(BuildInfo.MetadataKey.INPUT_BASED_RULE_KEY);
                        Optional<RuleKey> metaDataRuleKey = buildInfoRecorder
                                .getBuildMetadataFor(BuildInfo.MetadataKey.INPUT_BASED_RULE_KEY)
                                .map(RuleKey::new);
                        Preconditions.checkState(calculatedRuleKey.equals(onDiskRuleKey),
                                "%s (%s): %s: invalid on-disk input-based rule key: %s != %s",
                                rule.getBuildTarget(), rule.getType(), success, calculatedRuleKey,
                                onDiskRuleKey);
                        Preconditions.checkState(calculatedRuleKey.equals(metaDataRuleKey),
                                "%s: %s: invalid meta-data input-based rule key: %s != %s",
                                rule.getBuildTarget(), success, calculatedRuleKey, metaDataRuleKey);
                        if (calculatedRuleKey.isPresent()) {
                            ruleKeys.add(calculatedRuleKey.get());
                        }
                    }

                    // If the manifest-based rule key has changed, we need to push the artifact to cache
                    // using the new key.
                    if (useManifestCaching()) {
                        Optional<RuleKey> onDiskRuleKey = onDiskBuildInfo
                                .getRuleKey(BuildInfo.MetadataKey.DEP_FILE_RULE_KEY);
                        Optional<RuleKey> metaDataRuleKey = buildInfoRecorder
                                .getBuildMetadataFor(BuildInfo.MetadataKey.DEP_FILE_RULE_KEY).map(RuleKey::new);
                        Preconditions.checkState(onDiskRuleKey.equals(metaDataRuleKey),
                                "%s: %s: inconsistent meta-data and on-disk dep-file rule key: %s != %s",
                                rule.getBuildTarget(), success, onDiskRuleKey, metaDataRuleKey);
                        if (onDiskRuleKey.isPresent()) {
                            ruleKeys.add(onDiskRuleKey.get());
                        }
                    }

                    // Do the actual upload.
                    try {

                        // Verify that the recorded path hashes are accurate.
                        Optional<String> recordedPathHashes = buildInfoRecorder
                                .getBuildMetadataFor(BuildInfo.MetadataKey.RECORDED_PATH_HASHES);
                        if (recordedPathHashes.isPresent() && !verifyRecordedPathHashes(rule.getBuildTarget(),
                                rule.getProjectFilesystem(), recordedPathHashes.get())) {
                            return;
                        }

                        // Push to cache.
                        buildInfoRecorder.performUploadToArtifactCache(ImmutableSet.copyOf(ruleKeys),
                                buildContext.getArtifactCache(), buildContext.getEventBus());

                    } catch (Throwable t) {
                        buildContext.getEventBus().post(
                                ThrowableConsoleEvent.create(t, "Error uploading to cache for %s.", rule));
                    }
                }

                private void handleResult(BuildResult input) {
                    Optional<Long> outputSize = Optional.empty();
                    Optional<HashCode> outputHash = Optional.empty();
                    Optional<BuildRuleSuccessType> successType = Optional.empty();
                    boolean shouldUploadToCache = false;

                    BuildRuleEvent.Resumed resumedEvent = BuildRuleEvent.resumed(rule, buildRuleDurationTracker,
                            ruleKeyFactories.getDefaultRuleKeyFactory());
                    LOG.verbose(resumedEvent.toString());
                    buildContext.getEventBus().post(resumedEvent);

                    if (input.getStatus() == BuildRuleStatus.SUCCESS) {
                        BuildRuleSuccessType success = Preconditions.checkNotNull(input.getSuccess());
                        successType = Optional.of(success);

                        // Try get the output size.
                        if (success == BuildRuleSuccessType.BUILT_LOCALLY
                                || success.shouldUploadResultingArtifact()) {
                            try {
                                outputSize = Optional.of(buildInfoRecorder.getOutputSize());
                            } catch (IOException e) {
                                buildContext.getEventBus().post(ThrowableConsoleEvent.create(e,
                                        "Error getting output size for %s.", rule));
                            }
                        }

                        // Compute it's output hash for logging/tracing purposes, as this artifact will
                        // be consumed by other builds.
                        if (outputSize.isPresent() && shouldHashOutputs(success, outputSize.get())) {
                            try {
                                outputHash = Optional.of(buildInfoRecorder.getOutputHash(fileHashCache));
                            } catch (IOException e) {
                                buildContext.getEventBus().post(ThrowableConsoleEvent.create(e,
                                        "Error getting output hash for %s.", rule));
                            }
                        }

                        // Determine if this is rule is cacheable.
                        shouldUploadToCache = outputSize.isPresent()
                                && shouldUploadToCache(success, outputSize.get());

                        // Upload it to the cache.
                        if (shouldUploadToCache) {
                            uploadToCache(success);
                        }
                    }

                    boolean failureOrBuiltLocally = input.getStatus() == BuildRuleStatus.FAIL
                            || input.getSuccess() == BuildRuleSuccessType.BUILT_LOCALLY;
                    // Log the result to the event bus.
                    BuildRuleEvent.Finished finished = BuildRuleEvent.finished(resumedEvent, getBuildRuleKeys(),
                            input.getStatus(), input.getCacheResult(),
                            onDiskBuildInfo.getBuildValue(BuildInfo.MetadataKey.ORIGIN_BUILD_ID)
                                    .map(BuildId::new),
                            successType, shouldUploadToCache, outputHash, outputSize,
                            getBuildRuleDiagnosticData(failureOrBuiltLocally));
                    LOG.verbose(finished.toString());
                    buildContext.getEventBus().post(finished);
                }

                @Override
                public void onSuccess(BuildResult input) {
                    handleResult(input);

                    // Reset interrupted flag once failure has been recorded.
                    if (input.getFailure() instanceof InterruptedException) {
                        Threads.interruptCurrentThread();
                    }
                }

                @Override
                public void onFailure(@Nonnull Throwable thrown) {
                    throw new AssertionError("Dead code");
                }
            }, serviceByAdjustingDefaultWeightsTo(CachingBuildEngine.RULE_KEY_COMPUTATION_RESOURCE_AMOUNTS)));
    return buildResult;
}

From source file:com.facebook.buck.core.build.engine.impl.CachingBuildRuleBuilder.java

ListenableFuture<BuildResult> build() {
    AtomicReference<Long> outputSize = Atomics.newReference();

    ListenableFuture<List<BuildResult>> depResults = Futures.immediateFuture(Collections.emptyList());

    // If we're performing a deep build, guarantee that all dependencies will *always* get
    // materialized locally
    if (buildMode == BuildType.DEEP || buildMode == BuildType.POPULATE_FROM_REMOTE_CACHE) {
        depResults = buildRuleBuilderDelegate.getDepResults(rule, executionContext);
    }/*from  w  ww .  j a v  a  2s. c  o  m*/

    ListenableFuture<BuildResult> buildResult = Futures.transformAsync(depResults,
            input -> buildOrFetchFromCache(),
            serviceByAdjustingDefaultWeightsTo(CachingBuildEngine.SCHEDULING_MORE_WORK_RESOURCE_AMOUNTS));

    // Check immediately (without posting a new task) for a failure so that we can short-circuit
    // pending work. Use .catchingAsync() instead of .catching() so that we can propagate unchecked
    // exceptions.
    buildResult = Futures.catchingAsync(buildResult, Throwable.class, throwable -> {
        Objects.requireNonNull(throwable);
        BuildRuleFailedException failedException = getFailedException(throwable);
        buildRuleBuilderDelegate.setFirstFailure(failedException);
        throw failedException;
    });

    buildResult = Futures.transform(buildResult, (result) -> {
        buildRuleBuilderDelegate.markRuleAsUsed(rule, eventBus);
        return result;
    }, MoreExecutors.directExecutor());

    buildResult = Futures.transformAsync(buildResult,
            ruleAsyncFunction(result -> finalizeBuildRule(result, outputSize)),
            serviceByAdjustingDefaultWeightsTo(CachingBuildEngine.RULE_KEY_COMPUTATION_RESOURCE_AMOUNTS));

    buildResult = Futures.catchingAsync(buildResult, Throwable.class, thrown -> {
        String message = String.format("Building rule [%s] failed.", rule.getBuildTarget());
        BuildRuleFailedException failedException = getFailedException(thrown);
        LOG.debug(failedException, message);
        if (consoleLogBuildFailuresInline) {
            // TODO(cjhopman): This probably shouldn't be a thing. Why can't we just rely on the
            // propagated failure being printed?
            eventBus.post(ConsoleEvent.severe(message));
        }
        recordFailureAndCleanUp(failedException);
        return Futures.immediateFuture(failure(thrown));
    });

    // Do things that need to happen after either success or failure, but don't block the dependents
    // while doing so:
    buildRuleBuilderDelegate
            .addAsyncCallback(MoreFutures.addListenableCallback(buildResult, new FutureCallback<BuildResult>() {
                @Override
                public void onSuccess(BuildResult input) {
                    handleResult(input);

                    // Reset interrupted flag once failure has been recorded.
                    if (!input.isSuccess() && input.getFailure() instanceof InterruptedException) {
                        Threads.interruptCurrentThread();
                    }
                }

                @Override
                public void onFailure(@Nonnull Throwable thrown) {
                    throw new AssertionError("Dead code", thrown);
                }
            }, serviceByAdjustingDefaultWeightsTo(CachingBuildEngine.RULE_KEY_COMPUTATION_RESOURCE_AMOUNTS)));
    return buildResult;
}