/*
* Copyright 2016 the original author or authors.
*
* 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 org.gradle.caching.internal;
import org.gradle.api.GradleException;
import org.gradle.api.logging.configuration.LoggingConfiguration;
import org.gradle.api.logging.configuration.ShowStacktrace;
import org.gradle.caching.BuildCacheEntryReader;
import org.gradle.caching.BuildCacheEntryWriter;
import org.gradle.caching.BuildCacheException;
import org.gradle.caching.BuildCacheKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A decorator around a {@link org.gradle.caching.BuildCacheService} that passes through the underlying implementation
* until a number {@link BuildCacheException}s occur.
* The {@link BuildCacheException}s are counted and then ignored.
*
* After that the decorator short-circuits cache requests as no-ops.
*/
public class ShortCircuitingErrorHandlerBuildCacheServiceDecorator extends AbstractRoleAwareBuildCacheServiceDecorator {
private static final Logger LOGGER = LoggerFactory.getLogger(ShortCircuitingErrorHandlerBuildCacheServiceDecorator.class);
private boolean closed;
private final int maxErrorCount;
private final AtomicBoolean enabled = new AtomicBoolean(true);
private final AtomicInteger remainingErrorCount;
private final LoggingConfiguration loggingConfiguration;
private String disableMessage;
public ShortCircuitingErrorHandlerBuildCacheServiceDecorator(int maxErrorCount, LoggingConfiguration loggingConfiguration, RoleAwareBuildCacheService delegate) {
super(delegate);
this.maxErrorCount = maxErrorCount;
this.remainingErrorCount = new AtomicInteger(maxErrorCount);
this.loggingConfiguration = loggingConfiguration;
}
@Override
public boolean load(BuildCacheKey key, BuildCacheEntryReader reader) {
if (enabled.get()) {
try {
LOGGER.debug("Loading entry {} from {} build cache", key, getRole());
return super.load(key, reader);
} catch (BuildCacheException e) {
reportFailure("load", "from", key, e);
recordFailure();
// Assume cache didn't have it.
} catch (RuntimeException e) {
throw new GradleException("Could not load entry " + key + " from " + getRole() + " build cache", e);
}
}
return false;
}
@Override
public void store(BuildCacheKey key, BuildCacheEntryWriter writer) {
if (enabled.get()) {
try {
LOGGER.debug("Storing entry {} in {} build cache", key, getRole());
super.store(key, writer);
} catch (BuildCacheException e) {
reportFailure("store", "in", key, e);
recordFailure();
// Assume its OK to not push anything.
} catch (Exception e) {
reportFailure("store", "in", key, e);
disableBuildCache("a non-recoverable error was encountered.");
}
}
}
@Override
public void close() throws IOException {
LOGGER.debug("Closing {} build cache", getRole());
if (!closed) {
if (!enabled.get()) {
LOGGER.warn("The {} build cache was disabled during the build because {}", getRole(), disableMessage);
}
super.close();
}
closed = true;
}
private void reportFailure(String verb, String preposition, BuildCacheKey key, Exception e) {
if (!LOGGER.isWarnEnabled()) {
return;
}
if (loggingConfiguration.getShowStacktrace() == ShowStacktrace.INTERNAL_EXCEPTIONS) {
LOGGER.warn("Could not {} entry {} {} {} build cache: {}", verb, key, preposition, getRole(), e.getMessage());
} else {
LOGGER.warn("Could not {} entry {} {} {} build cache", verb, key, preposition, getRole(), e);
}
}
private void recordFailure() {
if (remainingErrorCount.decrementAndGet() <= 0) {
disableBuildCache(maxErrorCount + " recoverable errors were encountered.");
}
}
private void disableBuildCache(String message) {
if (enabled.compareAndSet(true, false)) {
disableMessage = message;
LOGGER.warn("The {} build cache is now disabled because {}", getRole(), message);
}
}
}