| // Copyright 2023 The Bazel Authors. All rights reserved. | |
| // | |
| // 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.google.devtools.build.lib.concurrent; | |
| import com.google.common.collect.Interner; | |
| import com.google.common.collect.MapMaker; | |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadHostile; | |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | |
| import com.google.errorprone.annotations.ForOverride; | |
| import java.lang.reflect.Field; | |
| import java.util.Collections; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import javax.annotation.Nullable; | |
| /** | |
| * An extension of the weak interner which uses a global pool in addition to the weak interner's | |
| * {@code ConcurrentHashMap} to store instances. | |
| * | |
| * <p>The reason of implementing {@link PooledInterner} is that the same object can be stored in | |
| * both weak interner and some other container ({@code InMemoryGraphImple#nodeMap}) in blaze with | |
| * two equal references, causing some memory overhead. | |
| * | |
| * <p>{@link PooledInterner} enables the client to manage where a single object is stored, | |
| * addressing the memory overhead issue. In more detail, | |
| * | |
| * <ul> | |
| * <li>If the object is already canonicalized in the global pool, it should not be stored in | |
| * {@link #weakInterner} again, thus removing the storage overhead of using a traditional weak | |
| * interner; | |
| * <li>User can also remove the object from {@link #weakInterner}'s underlying {@link | |
| * #internerAsMap} when the object appears in the global pool. | |
| * </ul> | |
| * | |
| * <p>Subclasses are only responsible for providing the appropriate {@link Pool} by overriding | |
| * {@link #getPool()} method. | |
| */ | |
| public abstract class PooledInterner<T> implements Interner<T> { | |
| private Interner<T> weakInterner = BlazeInterners.newWeakInterner(); | |
| private Map<?, ?> internerAsMap = getMapReflectively(weakInterner); | |
| /** | |
| * Holds all PooledInterner instances registered in the lifetime of this Blaze server as a weak | |
| * collection to prevent memory leaks. This is also thread-safe to handle multiple PooledInterners | |
| * from being instantiated concurrently, for instance during class initializations. | |
| */ | |
| private static final Set<PooledInterner<?>> instances = | |
| Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); | |
| protected PooledInterner() { | |
| instances.add(this); | |
| } | |
| /** | |
| * Interns {@code sample} directly into {@link #weakInterner} without checking the global pool and | |
| * returns the canonical instance of {@code sample}. | |
| */ | |
| @CanIgnoreReturnValue | |
| public final T weakIntern(T sample) { | |
| return weakInterner.intern(sample); | |
| } | |
| /** | |
| * Removes sample from the weak interner. Client can call this method when the sample is already | |
| * stored in the global pool in order to reduce the memory overhead. | |
| */ | |
| public final void removeWeak(Object sample) { | |
| internerAsMap.remove(sample); | |
| } | |
| /** | |
| * Returns the canonical instance of {@code sample} from either global pool or {@link | |
| * #weakInterner}. | |
| */ | |
| @Override | |
| public final T intern(T sample) { | |
| Pool<T> pool = getPool(); | |
| return pool != null ? pool.getOrWeakIntern(sample) : weakInterner.intern(sample); | |
| } | |
| /** | |
| * Provides the global pool instance for {@link #intern(Object)} method. | |
| */ | |
| @ForOverride | |
| @Nullable | |
| protected abstract Pool<T> getPool(); | |
| public final int size() { | |
| return internerAsMap.size(); | |
| } | |
| /** | |
| * Shrinks all interner instances' backing map to reclaim memory. | |
| * | |
| * <p>This needs a prior GC to be effective. | |
| * | |
| * <p>WARNING: This must not be called concurrently with any interning operations, because it | |
| * provides unsynchronized access to multiple mutable static interners. | |
| */ | |
| @ThreadHostile | |
| public static final void shrinkAll() { | |
| instances.forEach(PooledInterner::shrink); | |
| } | |
| /** Shrinks the weak interner and obtain a new reference to the newly shrunk map. */ | |
| private void shrink() { | |
| this.weakInterner = shrinkAsNewWeakInterner(weakInterner); | |
| this.internerAsMap = getMapReflectively(weakInterner); | |
| } | |
| /** | |
| * Shrink an interner by rebuilding a new weak interner and backing map/array. Use this if you | |
| * expect a GC to clear references into an interner's backing map. | |
| * | |
| * <p>If there are references to the backing map, use {@code getMapReflectively} to update them. | |
| * | |
| * <p>This is created because backing maps do not automatically resize after removing entries. | |
| */ | |
| private static <T> Interner<T> shrinkAsNewWeakInterner(Interner<T> fromInterner) { | |
| Interner<T> toInterner = BlazeInterners.newWeakInterner(); | |
| Map<T, ?> map = getMapReflectively(fromInterner); | |
| map.keySet().parallelStream() | |
| .forEach( | |
| k -> { | |
| T unused = toInterner.intern(k); | |
| }); | |
| return toInterner; | |
| } | |
| // Returns the backing map of an interner. | |
| // | |
| // There was a Guava API review to include the feature of removing from an interner, and the | |
| // outcome was that we should just get and manipulate the map reflectively. | |
| // | |
| // See the description for cl/623798951 for additional context. | |
| @SuppressWarnings("unchecked") | |
| private static <T> Map<T, ?> getMapReflectively(Interner<?> interner) { | |
| try { | |
| Field field = interner.getClass().getDeclaredField("map"); | |
| field.setAccessible(true); | |
| return (Map<T, ?>) field.get(interner); | |
| } catch (ReflectiveOperationException e) { | |
| throw new IllegalStateException(e); | |
| } | |
| } | |
| /** | |
| * An alternative container to the weak interner for storing type T instance. | |
| * | |
| * <p>A pool is a storage space that already exists during normal program execution and provides | |
| * lookup functionality for interning, thus eliminating storage overhead from using a classic weak | |
| * interner. | |
| */ | |
| public interface Pool<T> { | |
| /** | |
| * Returns the canonical instance for the given key in the pool if it is present, otherwise | |
| * interns the key using its {@linkplain #weakIntern weak interner}. | |
| * | |
| * <p>To ensure a single canonical instance, if the key is not present in the pool, it should be | |
| * weakly interned using synchronization so that it is not concurrently {@linkplain #removeWeak | |
| * removed from the weak interner}. | |
| */ | |
| T getOrWeakIntern(T sample); | |
| } | |
| } |