com.facebook.buck.parser.DaemonicCellState.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.parser.DaemonicCellState.java

Source

/*
 * Copyright 2016-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.parser;

import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetException;
import com.facebook.buck.model.UnflavoredBuildTarget;
import com.facebook.buck.rules.Cell;
import com.facebook.buck.util.concurrent.AutoCloseableLock;
import com.facebook.buck.util.concurrent.AutoCloseableReadWriteUpdateLock;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;

import javax.annotation.concurrent.GuardedBy;

class DaemonicCellState {

    private static final Logger LOG = Logger.get(DaemonicCellState.class);

    private class CacheImpl<T> implements PipelineNodeCache.Cache<BuildTarget, T> {

        @GuardedBy("rawAndComputedNodesLock")
        public final ConcurrentMapCache<BuildTarget, T> allComputedNodes = new ConcurrentMapCache<>(parsingThreads);

        @Override
        public Optional<T> lookupComputedNode(Cell cell, BuildTarget target) throws BuildTargetException {
            try (AutoCloseableLock readLock = rawAndComputedNodesLock.readLock()) {
                return Optional.ofNullable(allComputedNodes.getIfPresent(target));
            }
        }

        @Override
        public T putComputedNodeIfNotPresent(Cell cell, BuildTarget target, T targetNode)
                throws BuildTargetException {
            try (AutoCloseableLock writeLock = rawAndComputedNodesLock.writeLock()) {
                T updatedNode = allComputedNodes.putIfAbsentAndGet(target, targetNode);
                if (updatedNode.equals(targetNode)) {
                    if (cell.getBuckConfig().getView(ParserConfig.class).getTrackCellAgnosticTarget()) {
                        targetsCornucopia.put(target.withoutCell().getUnflavoredBuildTarget(), target);
                    } else {
                        targetsCornucopia.put(target.getUnflavoredBuildTarget(), target);
                    }
                }
                return updatedNode;
            }
        }
    }

    private final Path cellRoot;
    private Cell cell;

    @GuardedBy("rawAndComputedNodesLock")
    private final SetMultimap<Path, Path> buildFileDependents;
    @GuardedBy("rawAndComputedNodesLock")
    private final SetMultimap<UnflavoredBuildTarget, BuildTarget> targetsCornucopia;
    @GuardedBy("rawAndComputedNodesLock")
    private final Map<Path, ImmutableMap<String, ImmutableMap<String, Optional<String>>>> buildFileConfigs;
    @GuardedBy("rawAndComputedNodesLock")
    private final Map<Path, ImmutableMap<String, Optional<String>>> buildFileEnv;
    @GuardedBy("rawAndComputedNodesLock")
    private final ConcurrentMapCache<Path, ImmutableSet<Map<String, Object>>> allRawNodes;
    @GuardedBy("rawAndComputedNodesLock")
    private final ConcurrentMap<Class<?>, CacheImpl<?>> typedNodeCaches;

    private final AutoCloseableReadWriteUpdateLock rawAndComputedNodesLock;
    private final int parsingThreads;

    DaemonicCellState(Cell cell, int parsingThreads) {
        this.cell = cell;
        this.parsingThreads = parsingThreads;
        this.cellRoot = cell.getRoot();
        this.buildFileDependents = HashMultimap.create();
        this.targetsCornucopia = HashMultimap.create();
        this.buildFileConfigs = new HashMap<>();
        this.buildFileEnv = new HashMap<>();
        this.allRawNodes = new ConcurrentMapCache<>(parsingThreads);
        this.typedNodeCaches = Maps.newConcurrentMap();
        this.rawAndComputedNodesLock = new AutoCloseableReadWriteUpdateLock();
    }

    // TODO(mzlee): Only needed for invalidateBasedOn which does not have access to cell metadata
    Cell getCell() {
        return cell;
    }

    Path getCellRoot() {
        return cellRoot;
    }

    @SuppressWarnings("unchecked")
    public <T> CacheImpl<T> getOrCreateCache(Class<T> type) {
        try (AutoCloseableLock updateLock = rawAndComputedNodesLock.updateLock()) {
            CacheImpl<?> cache = typedNodeCaches.get(type);
            if (cache == null) {
                try (AutoCloseableLock writeLock = rawAndComputedNodesLock.writeLock()) {
                    cache = new CacheImpl<>();
                    typedNodeCaches.put(type, cache);
                }
            }
            return (CacheImpl<T>) cache;
        }
    }

    @SuppressWarnings("unchecked")
    public <T> CacheImpl<T> getCache(Class<T> type) {
        try (AutoCloseableLock readLock = rawAndComputedNodesLock.readLock()) {
            return (CacheImpl<T>) typedNodeCaches.get(type);
        }
    }

    Optional<ImmutableSet<Map<String, Object>>> lookupRawNodes(Path buildFile) {
        try (AutoCloseableLock readLock = rawAndComputedNodesLock.readLock()) {
            return Optional.ofNullable(allRawNodes.getIfPresent(buildFile));
        }
    }

    ImmutableSet<Map<String, Object>> putRawNodesIfNotPresentAndStripMetaEntries(final Path buildFile,
            final ImmutableSet<Map<String, Object>> withoutMetaIncludes,
            final ImmutableSet<Path> dependentsOfEveryNode,
            ImmutableMap<String, ImmutableMap<String, Optional<String>>> configs,
            ImmutableMap<String, Optional<String>> env) {
        try (AutoCloseableLock writeLock = rawAndComputedNodesLock.writeLock()) {
            ImmutableSet<Map<String, Object>> updated = allRawNodes.putIfAbsentAndGet(buildFile,
                    withoutMetaIncludes);
            buildFileConfigs.put(buildFile, configs);
            buildFileEnv.put(buildFile, env);
            if (updated == withoutMetaIncludes) {
                // We now know all the nodes. They all implicitly depend on everything in
                // the "dependentsOfEveryNode" set.
                for (Path dependent : dependentsOfEveryNode) {
                    buildFileDependents.put(dependent, buildFile);
                }
            }
            return updated;
        }
    }

    int invalidatePath(Path path) {
        try (AutoCloseableLock writeLock = rawAndComputedNodesLock.writeLock()) {
            int invalidatedRawNodes = 0;
            ImmutableSet<Map<String, Object>> rawNodes = allRawNodes.getIfPresent(path);
            if (rawNodes != null) {
                // Increment the counter
                invalidatedRawNodes = rawNodes.size();
                for (Map<String, Object> rawNode : rawNodes) {
                    UnflavoredBuildTarget target = RawNodeParsePipeline.parseBuildTargetFromRawRule(cell.getRoot(),
                            rawNode, path);
                    LOG.debug("Invalidating target for path %s: %s", path, target);
                    for (CacheImpl<?> cache : typedNodeCaches.values()) {
                        cache.allComputedNodes.invalidateAll(targetsCornucopia.get(target));
                    }
                    targetsCornucopia.removeAll(target);
                }
                allRawNodes.invalidate(path);
            }

            // We may have been given a file that other build files depend on. Iteratively remove those.
            Iterable<Path> dependents = buildFileDependents.get(path);
            LOG.debug("Invalidating dependents for path %s: %s", path, dependents);
            for (Path dependent : dependents) {
                if (dependent.equals(path)) {
                    continue;
                }
                invalidatedRawNodes += invalidatePath(dependent);
            }
            buildFileDependents.removeAll(path);
            buildFileConfigs.remove(path);
            buildFileEnv.remove(path);

            return invalidatedRawNodes;
        }
    }

    void invalidateIfBuckConfigHasChanged(Cell cell, Path buildFile) {
        try (AutoCloseableLock writeLock = rawAndComputedNodesLock.writeLock()) {
            // TODO(mzlee): Check whether usedConfigs includes the buildFileName
            ImmutableMap<String, ImmutableMap<String, Optional<String>>> usedConfigs = buildFileConfigs
                    .get(buildFile);
            if (usedConfigs == null) {
                // TODO(mzlee): Figure out when/how we can safely update this
                this.cell = cell;
                return;
            }
            for (Map.Entry<String, ImmutableMap<String, Optional<String>>> keyEnt : usedConfigs.entrySet()) {
                for (Map.Entry<String, Optional<String>> valueEnt : keyEnt.getValue().entrySet()) {
                    Optional<String> value = cell.getBuckConfig().getValue(keyEnt.getKey(), valueEnt.getKey());
                    if (!value.equals(valueEnt.getValue())) {
                        invalidatePath(buildFile);
                        this.cell = cell;
                        return;
                    }
                }
            }
        }
    }

    Optional<MapDifference<String, String>> invalidateIfEnvHasChanged(Cell cell, Path buildFile) {
        try (AutoCloseableLock writeLock = rawAndComputedNodesLock.writeLock()) {
            // Invalidate if env vars have changed.
            ImmutableMap<String, Optional<String>> usedEnv = buildFileEnv.get(buildFile);
            if (usedEnv == null) {
                this.cell = cell;
                return Optional.empty();
            }
            for (Map.Entry<String, Optional<String>> ent : usedEnv.entrySet()) {
                Optional<String> value = Optional
                        .ofNullable(cell.getBuckConfig().getEnvironment().get(ent.getKey()));
                if (!value.equals(ent.getValue())) {
                    invalidatePath(buildFile);
                    this.cell = cell;
                    return Optional.of(Maps.difference(
                            value.map(v -> ImmutableMap.of(ent.getKey(), v)).orElse(ImmutableMap.of()),
                            ent.getValue().map(v -> ImmutableMap.of(ent.getKey(), v)).orElse(ImmutableMap.of())));
                }
            }
            return Optional.empty();
        }
    }

}