/* * Copyright 2017 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.cache; import net.jcip.annotations.ThreadSafe; import org.gradle.api.Nullable; import org.gradle.api.Transformer; import org.gradle.initialization.SessionLifecycleListener; import org.gradle.internal.event.ListenerManager; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * A factory for {@link CrossBuildInMemoryCache} instances. * * Note that this implementation should only be used to create global scoped services. * Note that this implementation currently retains strong references to keys and values during the whole lifetime of a build session. * * Uses a simple algorithm to collect unused values, by retaining strong references to all keys and values used during the current build session, and the previous build session. All other values are referenced only by soft references. */ @ThreadSafe public class CrossBuildInMemoryCacheFactory { private final ListenerManager listenerManager; public CrossBuildInMemoryCacheFactory(ListenerManager listenerManager) { this.listenerManager = listenerManager; } /** * Creates a new cache instance. * * Note: this should be used to create _only_ global scoped instances. */ public <K, V> CrossBuildInMemoryCache<K, V> newCache() { DefaultCrossBuildInMemoryCache<K, V> cache = new DefaultCrossBuildInMemoryCache<K, V>(); listenerManager.addListener(cache); return cache; } private static class DefaultCrossBuildInMemoryCache<K, V> implements CrossBuildInMemoryCache<K, V>, SessionLifecycleListener { private final Object lock = new Object(); private final Map<K, V> valuesForThisSession = new HashMap<K, V>(); // This is used only to retain strong references to the values private final Set<V> valuesForPreviousSession = new HashSet<V>(); private final Map<K, SoftReference<V>> allValues = new HashMap<K, SoftReference<V>>(); @Override public void afterStart() { } @Override public void beforeComplete() { synchronized (lock) { // Retain strong references to the values created for this session valuesForPreviousSession.clear(); valuesForPreviousSession.addAll(valuesForThisSession.values()); valuesForThisSession.clear(); } } @Override public void clear() { synchronized (lock) { valuesForThisSession.clear(); valuesForPreviousSession.clear(); allValues.clear(); } } @Nullable @Override public V get(K key) { synchronized (lock) { return getIfPresent(key); } } @Override public V get(K key, Transformer<V, K> factory) { synchronized (lock) { V v = getIfPresent(key); if (v != null) { return v; } // TODO - do not hold lock while computing value v = factory.transform(key); allValues.put(key, new SoftReference<V>(v)); // Retain strong reference valuesForThisSession.put(key, v); return v; } } @Override public void put(K key, V value) { synchronized (lock) { allValues.put(key, new SoftReference<V>(value)); valuesForThisSession.put(key, value); } } // Caller must be holding lock private V getIfPresent(K key) { V v = valuesForThisSession.get(key); if (v != null) { return v; } SoftReference<V> reference = allValues.get(key); if (reference != null) { v = reference.get(); if (v != null) { // Retain strong reference valuesForThisSession.put(key, v); return v; } } return null; } } }