/*
* Copyright 2012-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.artifact_cache;
import com.facebook.buck.io.BorrowablePath;
import com.facebook.buck.io.LazyPath;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.util.MoreCollectors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.Optional;
/**
* MultiArtifactCache encapsulates a set of ArtifactCache instances such that fetch() succeeds if
* any of the ArtifactCaches contain the desired artifact, and store() applies to all
* ArtifactCaches.
*/
public class MultiArtifactCache implements ArtifactCache {
private final ImmutableList<ArtifactCache> artifactCaches;
private final ImmutableList<ArtifactCache> writableArtifactCaches;
private final boolean isStoreSupported;
public MultiArtifactCache(ImmutableList<ArtifactCache> artifactCaches) {
this.artifactCaches = artifactCaches;
this.writableArtifactCaches =
artifactCaches
.stream()
.filter(c -> c.getCacheReadMode().equals(CacheReadMode.READWRITE))
.collect(MoreCollectors.toImmutableList());
this.isStoreSupported = this.writableArtifactCaches.size() > 0;
}
/**
* Fetch the artifact matching ruleKey and store it to output. If any of the encapsulated
* ArtifactCaches contains the desired artifact, this method succeeds, and it may store the
* artifact to one or more of the other encapsulated ArtifactCaches as a side effect.
*/
@Override
public CacheResult fetch(RuleKey ruleKey, LazyPath output) {
CacheResult cacheResult = CacheResult.miss();
ImmutableList.Builder<ArtifactCache> priorCaches = ImmutableList.builder();
for (ArtifactCache artifactCache : artifactCaches) {
cacheResult = artifactCache.fetch(ruleKey, output);
if (cacheResult.getType().isSuccess()) {
break;
}
if (artifactCache.getCacheReadMode().isWritable()) {
priorCaches.add(artifactCache);
}
}
if (cacheResult.getType().isSuccess()) {
// Success; terminate search for a cached artifact, and propagate artifact to caches
// earlier in the search order so that subsequent searches terminate earlier.
storeToCaches(
priorCaches.build(),
ArtifactInfo.builder()
.addRuleKeys(ruleKey)
.setMetadata(cacheResult.getMetadata())
.build(),
BorrowablePath.notBorrowablePath(output.getUnchecked()));
}
return cacheResult;
}
private static ListenableFuture<Void> storeToCaches(
ImmutableList<ArtifactCache> caches, ArtifactInfo info, BorrowablePath output) {
// TODO(cjhopman): support BorrowablePath with multiple writable caches.
if (caches.size() != 1) {
output = BorrowablePath.notBorrowablePath(output.getPath());
}
List<ListenableFuture<Void>> storeFutures = Lists.newArrayListWithExpectedSize(caches.size());
for (ArtifactCache artifactCache : caches) {
storeFutures.add(artifactCache.store(info, output));
}
// Aggregate future to ensure all store operations have completed.
return Futures.transform(Futures.allAsList(storeFutures), Functions.<Void>constant(null));
}
/** Store the artifact to all encapsulated ArtifactCaches. */
@Override
public ListenableFuture<Void> store(ArtifactInfo info, BorrowablePath output) {
return storeToCaches(writableArtifactCaches, info, output);
}
@Override
public CacheReadMode getCacheReadMode() {
return isStoreSupported ? CacheReadMode.READWRITE : CacheReadMode.READONLY;
}
@Override
public void close() {
Optional<RuntimeException> throwable = Optional.empty();
for (ArtifactCache artifactCache : artifactCaches) {
try {
artifactCache.close();
} catch (RuntimeException e) {
throwable = Optional.of(e);
}
}
if (throwable.isPresent()) {
throw throwable.get();
}
}
@VisibleForTesting
ImmutableList<ArtifactCache> getArtifactCaches() {
return ImmutableList.copyOf(artifactCaches);
}
}