blob: 89e45ca8cb8ea05613a2b539c5c2a89e5ec61047 [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.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.ForOverride;
import java.lang.reflect.Field;
import java.util.Map;
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 final Interner<T> weakInterner = BlazeInterners.newWeakInterner();
private final Map<?, ?> internerAsMap = getMapReflectively(weakInterner);
protected PooledInterner() {}
// There was a Guava API review to include the feature of removing from an interner, and the
// outcome was that we should just get the map reflectively.
private static Map<?, ?> getMapReflectively(Interner<?> interner) {
try {
Field field = interner.getClass().getDeclaredField("map");
field.setAccessible(true);
return (Map<?, ?>) field.get(interner);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
/**
* 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();
/**
* 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);
}
}