blob: 09857c8179a31756c4f10114b374c631b34c5ec8 [file] [log] [blame]
// 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);
}
}