/* * Copyright 2013 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.api.internal.changedetection.state; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.gradle.api.Transformer; import org.gradle.api.internal.cache.CrossBuildInMemoryCache; import org.gradle.api.internal.cache.CrossBuildInMemoryCacheFactory; import org.gradle.api.internal.cache.HeapProportionalCacheSizer; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.cache.internal.AsyncCacheAccess; import org.gradle.cache.internal.AsyncCacheAccessDecoratedCache; import org.gradle.cache.internal.CacheDecorator; import org.gradle.cache.internal.CrossProcessCacheAccess; import org.gradle.cache.internal.CrossProcessSynchronizingCache; import org.gradle.cache.internal.FileLock; import org.gradle.cache.internal.MultiProcessSafeAsyncPersistentIndexedCache; import org.gradle.cache.internal.MultiProcessSafePersistentIndexedCache; import java.util.concurrent.atomic.AtomicReference; /** * A {@link CacheDecorator} that wraps each cache with an in-memory cache that is used to short-circuit reads from the backing cache. * The in-memory cache is invalidated when the backing cache is changed by another process. * * Also decorates each cache so that updates to the backing cache are made asynchronously. */ public class InMemoryCacheDecoratorFactory { private final static Logger LOG = Logging.getLogger(InMemoryCacheDecoratorFactory.class); private final boolean longLivingProcess; private final HeapProportionalCacheSizer cacheSizer = new HeapProportionalCacheSizer(); private final CrossBuildInMemoryCache<String, CacheDetails> caches; public InMemoryCacheDecoratorFactory(boolean longLivingProcess, CrossBuildInMemoryCacheFactory cacheFactory) { this.longLivingProcess = longLivingProcess; caches = cacheFactory.newCache(); } public CacheDecorator decorator(final int maxEntriesToKeepInMemory, final boolean cacheInMemoryForShortLivedProcesses) { return new InMemoryCacheDecorator(maxEntriesToKeepInMemory, cacheInMemoryForShortLivedProcesses); } private <K, V> MultiProcessSafeAsyncPersistentIndexedCache<K, V> applyInMemoryCaching(String cacheId, MultiProcessSafeAsyncPersistentIndexedCache<K, V> backingCache, int maxEntriesToKeepInMemory, boolean cacheInMemoryForShortLivedProcesses) { if (!longLivingProcess && !cacheInMemoryForShortLivedProcesses) { // Short lived process, don't cache in memory LOG.debug("Creating cache {} without in-memory store.", cacheId); return backingCache; } int targetSize = cacheSizer.scaleCacheSize(maxEntriesToKeepInMemory); CacheDetails cacheDetails = getCache(cacheId, targetSize); return new InMemoryDecoratedCache<K, V>(backingCache, cacheDetails.entries, cacheId, cacheDetails.lockState); } private CacheDetails getCache(final String cacheId, final int maxSize) { CacheDetails cacheDetails = caches.get(cacheId, new Transformer<CacheDetails, String>() { @Override public CacheDetails transform(String cacheId) { Cache<Object, Object> entries = createInMemoryCache(cacheId, maxSize); CacheDetails cacheDetails = new CacheDetails(cacheId, maxSize, entries, new AtomicReference<FileLock.State>(null)); LOG.debug("Creating in-memory store for cache {} (max size: {})", cacheId, maxSize); return cacheDetails; } }); if (cacheDetails.maxEntries != maxSize) { throw new IllegalStateException("Mismatched in-memory store size for cache " + cacheId + ", expected: " + maxSize + ", found: " + cacheDetails.maxEntries); } return cacheDetails; } private Cache<Object, Object> createInMemoryCache(String cacheId, int maxSize) { LoggingEvictionListener evictionListener = new LoggingEvictionListener(cacheId, maxSize); final CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder().maximumSize(maxSize).recordStats().removalListener(evictionListener); Cache<Object, Object> inMemoryCache = cacheBuilder.build(); evictionListener.setCache(inMemoryCache); return inMemoryCache; } private class InMemoryCacheDecorator implements CacheDecorator { private final int maxEntriesToKeepInMemory; private final boolean cacheInMemoryForShortLivedProcesses; InMemoryCacheDecorator(int maxEntriesToKeepInMemory, boolean cacheInMemoryForShortLivedProcesses) { this.maxEntriesToKeepInMemory = maxEntriesToKeepInMemory; this.cacheInMemoryForShortLivedProcesses = cacheInMemoryForShortLivedProcesses; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || obj.getClass() != getClass()) { return false; } InMemoryCacheDecorator other = (InMemoryCacheDecorator) obj; return maxEntriesToKeepInMemory == other.maxEntriesToKeepInMemory && cacheInMemoryForShortLivedProcesses == other.cacheInMemoryForShortLivedProcesses; } @Override public int hashCode() { return maxEntriesToKeepInMemory ^ (cacheInMemoryForShortLivedProcesses ? 1 : 0); } @Override public <K, V> MultiProcessSafePersistentIndexedCache<K, V> decorate(String cacheId, String cacheName, MultiProcessSafePersistentIndexedCache<K, V> persistentCache, CrossProcessCacheAccess crossProcessCacheAccess, AsyncCacheAccess asyncCacheAccess) { MultiProcessSafeAsyncPersistentIndexedCache<K, V> asyncCache = new AsyncCacheAccessDecoratedCache<K, V>(asyncCacheAccess, persistentCache); MultiProcessSafeAsyncPersistentIndexedCache<K, V> memCache = applyInMemoryCaching(cacheId, asyncCache, maxEntriesToKeepInMemory, cacheInMemoryForShortLivedProcesses); return new CrossProcessSynchronizingCache<K, V>(memCache, crossProcessCacheAccess); } } private static class CacheDetails { private final String cacheId; private final int maxEntries; private final Cache<Object, Object> entries; private final AtomicReference<FileLock.State> lockState; CacheDetails(String cacheId, int maxEntries, Cache<Object, Object> entries, AtomicReference<FileLock.State> lockState) { this.cacheId = cacheId; this.maxEntries = maxEntries; this.entries = entries; this.lockState = lockState; } } }