com.facebook.buck.rules.BuildRulePipelinesRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.rules.BuildRulePipelinesRunner.java

Source

/*
 * Copyright 2017-present Facebook, 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.facebook.buck.rules;

import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import javax.annotation.Nullable;

/** Constructs build rule pipelines for a single build. */
class BuildRulePipelinesRunner {
    private final ConcurrentHashMap<SupportsPipelining<? extends RulePipelineState>, BuildRulePipelineStage<? extends RulePipelineState>> rules = new ConcurrentHashMap<>();

    /** Gives the factory a way to construct a {@link RunnableWithFuture} to build the given rule. */
    public <T extends RulePipelineState> void addRule(SupportsPipelining<T> rule,
            Function<T, RunnableWithFuture<Optional<BuildResult>>> ruleStepRunnerFactory) {
        BuildRulePipelineStage<T> pipelineStage = getPipelineStage(rule);

        SupportsPipelining<T> previousRuleInPipeline = rule.getPreviousRuleInPipeline();
        if (previousRuleInPipeline != null) {
            Preconditions.checkState(
                    previousRuleInPipeline.getPipelineStateFactory() == rule.getPipelineStateFactory(),
                    "To help ensure that rules have pipeline-compatible rule keys, all rules in a pipeline must share a PipelineStateFactory instance.");
            Preconditions.checkState(
                    Sets.difference(rule.getBuildDeps(), previousRuleInPipeline.getBuildDeps())
                            .equals(Collections.singleton(previousRuleInPipeline)),
                    "Each rule in a pipeline cannot depend on rules which are not also dependencies of the previous rule in the pipeline. This ensures that each rule in the pipeline is ready to build as soon as the previous one completes.");
            getPipelineStage(previousRuleInPipeline).setNextStage(pipelineStage);
        }

        pipelineStage.setRuleStepRunnerFactory(ruleStepRunnerFactory);
    }

    public void removeRule(SupportsPipelining<?> rule) {
        rules.remove(rule);
    }

    public boolean runningPipelinesContainRule(SupportsPipelining<?> rule) {
        BuildRulePipelineStage<?> pipelineStage = getPipelineStage(rule);
        return pipelineStage.pipelineBuilt();
    }

    public ListenableFuture<Optional<BuildResult>> getFuture(SupportsPipelining<?> rule) {
        BuildRulePipelineStage<?> pipelineStage = getPipelineStage(rule);
        Preconditions.checkState(pipelineStage.pipelineBuilt());
        return pipelineStage.getFuture();
    }

    public <T extends RulePipelineState> ListenableFuture<Optional<BuildResult>> runPipelineStartingAt(
            SupportsPipelining<T> rootRule, ExecutorService executor) {
        RunnableWithFuture<Optional<BuildResult>> runner = newPipelineRunner(rootRule);
        executor.execute(runner);
        return runner.getFuture();
    }

    private <T extends RulePipelineState> RunnableWithFuture<Optional<BuildResult>> newPipelineRunner(
            SupportsPipelining<T> rootRule) {
        BuildRulePipelineStage<T> rootPipelineStage = getPipelineStage(rootRule);
        Preconditions.checkState(!rootPipelineStage.pipelineBuilt());

        BuildRulePipeline<T> pipeline = new BuildRulePipeline<>(rootPipelineStage,
                rootRule.getPipelineStateFactory().newInstance());
        return new RunnableWithFuture<Optional<BuildResult>>() {
            @Override
            public ListenableFuture<Optional<BuildResult>> getFuture() {
                // This runner is created when building the root rule locally. It gives back the future for
                // that root rule so that the dependents of that rule can begin compiling, while this
                // runner actually continues building the rest of the pipeline.
                return rootPipelineStage.getFuture();
            }

            @Override
            public void run() {
                pipeline.run();
            }
        };
    }

    private <T extends RulePipelineState> BuildRulePipelineStage<T> getPipelineStage(SupportsPipelining<T> rule) {
        @SuppressWarnings("unchecked")
        BuildRulePipelineStage<T> result = (BuildRulePipelineStage<T>) rules.computeIfAbsent(rule,
                key -> new BuildRulePipelineStage<T>());

        return result;
    }

    /**
     * Runs a list of rules one after another on the same thread, allowing each to access shared
     * state.
     */
    private static class BuildRulePipeline<T extends RulePipelineState> implements Runnable {
        private final T state;
        private final List<BuildRulePipelineStage<T>> rules = new ArrayList<>();

        public BuildRulePipeline(BuildRulePipelineStage<T> rootRule, T state) {
            this.state = state;

            buildPipeline(rootRule);
        }

        private void buildPipeline(BuildRulePipelineStage<T> firstStage) {
            BuildRulePipelineStage<T> walker = firstStage;

            while (walker != null) {
                rules.add(walker);
                walker.setPipeline(this);
                walker = walker.getNextStage();
            }
        }

        public T getState() {
            return state;
        }

        @Override
        public void run() {
            try {
                Throwable error = null;
                for (BuildRulePipelineStage<T> rule : rules) {
                    if (error == null) {
                        rule.run();
                        error = rule.getError();
                    } else {
                        // It doesn't really matter what error we use here -- we just want the future to
                        // complete so that Buck doesn't hang. We use the real error in case it ever is shown
                        // to the user (which does not happen as of the time of this comment, but for safety).
                        rule.abort(error);
                    }
                    // If everything is working correctly, each rule in the pipeline should show itself
                    // complete before we start the next one. Just a sanity check against weird behavior
                    // creeping in.
                    Preconditions.checkState(rule.getFuture().isDone() || rule.getFuture().isCancelled());
                }
            } finally {
                state.close();
            }
        }
    }

    /**
     * Creates and runs the steps for a single build rule within a pipeline, cascading any failures to
     * rules later in the pipeline.
     */
    private static class BuildRulePipelineStage<T extends RulePipelineState>
            implements RunnableWithFuture<Optional<BuildResult>> {
        private final SettableFuture<Optional<BuildResult>> future = SettableFuture.create();
        @Nullable
        private BuildRulePipelineStage<T> nextStage;
        @Nullable
        private Throwable error = null;
        @Nullable
        private Function<T, RunnableWithFuture<Optional<BuildResult>>> ruleStepRunnerFactory;
        @Nullable
        private T pipelineState;
        @Nullable
        private RunnableWithFuture<Optional<BuildResult>> runner;

        private BuildRulePipelineStage() {
            Futures.catching(future, Throwable.class, throwable -> error = throwable);
        }

        public void setRuleStepRunnerFactory(
                Function<T, RunnableWithFuture<Optional<BuildResult>>> ruleStepsFactory) {
            Preconditions.checkState(this.ruleStepRunnerFactory == null);
            this.ruleStepRunnerFactory = ruleStepsFactory;
        }

        public void setPipeline(BuildRulePipeline<T> pipeline) {
            Preconditions.checkState(this.pipelineState == null);
            this.pipelineState = pipeline.getState();
        }

        public void setNextStage(BuildRulePipelineStage<T> nextStage) {
            Preconditions.checkState(this.nextStage == null);
            this.nextStage = nextStage;
        }

        @Nullable
        public BuildRulePipelineStage<T> getNextStage() {
            return nextStage;
        }

        public boolean pipelineBuilt() {
            return pipelineState != null;
        }

        @Nullable
        public Throwable getError() {
            return error;
        }

        @Override
        public SettableFuture<Optional<BuildResult>> getFuture() {
            return future;
        }

        @Override
        public void run() {
            Preconditions.checkNotNull(pipelineState);
            Preconditions.checkNotNull(ruleStepRunnerFactory);

            runner = ruleStepRunnerFactory.apply(pipelineState);
            future.setFuture(runner.getFuture());
            runner.run();
        }

        public void abort(Throwable error) {
            future.setException(error);
        }
    }
}