/* * 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.model.BuildTargetException; import com.facebook.buck.rules.Cell; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; class PipelineNodeCache<K, T> { private final Cache<K, T> cache; protected final ConcurrentMap<K, ListenableFuture<T>> jobsCache; public PipelineNodeCache(Cache<K, T> cache) { this.jobsCache = new ConcurrentHashMap<>(); this.cache = cache; } /** * Helper for de-duping jobs against the cache. * * @param jobSupplier a supplier to use to create the actual job. * @return future describing the job. It can either be an immediate future (result cache hit), * ongoing job (job cache hit) or a new job (miss). */ protected final ListenableFuture<T> getJobWithCacheLookup( final Cell cell, final K key, JobSupplier<T> jobSupplier) throws BuildTargetException { Optional<T> cacheLookupResult = cache.lookupComputedNode(cell, key); if (cacheLookupResult.isPresent()) { return Futures.immediateFuture(cacheLookupResult.get()); } ListenableFuture<T> speculativeCacheLookupResult = jobsCache.get(key); if (speculativeCacheLookupResult != null) { return speculativeCacheLookupResult; } // We use a SettableFuture to resolve any races between threads that are trying to create the // job for the given key. The SettableFuture is cheap to throw away in case we didn't "win" and // can be easily "connected" to a future that actually does work in case we did. SettableFuture<T> resultFutureCandidate = SettableFuture.create(); ListenableFuture<T> resultFutureInCache = jobsCache.putIfAbsent(key, resultFutureCandidate); if (resultFutureInCache != null) { // Another thread succeeded in putting the new value into the cache. return resultFutureInCache; } // Ok, "our" candidate future went into the jobsCache, schedule the job and 'chain' the result // to the SettableFuture, so that anyone else waiting on it will get the same result. final SettableFuture<T> resultFuture = resultFutureCandidate; try { ListenableFuture<T> nodeJob = Futures.transformAsync( jobSupplier.get(), input -> Futures.immediateFuture(cache.putComputedNodeIfNotPresent(cell, key, input))); resultFuture.setFuture(nodeJob); } catch (Throwable t) { resultFuture.setException(t); throw t; } return resultFuture; } protected interface JobSupplier<V> { ListenableFuture<V> get() throws BuildTargetException; } public interface Cache<K, V> { Optional<V> lookupComputedNode(Cell cell, K target) throws BuildTargetException; /** * Insert item into the cache if it was not already there. * * @param cell cell * @param target target of the node * @param targetNode node to insert * @return previous node for the target if the cache contained it, new one otherwise. */ V putComputedNodeIfNotPresent(Cell cell, K target, V targetNode) throws BuildTargetException; } }