
io.opentelemetry.javaagent.tooling.muzzle.AgentCachingPoolStrategy Maven / Gradle / Ivy
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.muzzle;
import io.opentelemetry.instrumentation.api.cache.Cache;
import java.lang.ref.WeakReference;
import javax.annotation.Nullable;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.pool.TypePool;
/**
*
*
*
* There two core parts to the cache...
* - a cache of ClassLoader to WeakReference<ClassLoader>
*
- a single cache of TypeResolutions for all ClassLoaders - keyed by a custom composite key of
* ClassLoader and class name
*
*
* This design was chosen to create a single limited size cache that can be adjusted for the
* entire application -- without having to create a large number of WeakReference objects.
*
*
Eviction is handled through a size restriction
*/
public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy {
// Many things are package visible for testing purposes --
// others to avoid creation of synthetic accessors
static final int TYPE_CAPACITY = 64;
static final int BOOTSTRAP_HASH = 7236344; // Just a random number
/**
* Cache of recent ClassLoader WeakReferences; used to...
*
*
* - Reduced number of WeakReferences created
*
- Allow for quick fast path equivalence check of composite keys
*
*/
final Cache> loaderRefCache = Cache.weak();
/**
* Single shared Type.Resolution cache -- uses a composite key -- conceptually of loader & name
*/
final Cache sharedResolutionCache =
Cache.bounded(TYPE_CAPACITY);
// fast path for bootstrap
final SharedResolutionCacheAdapter bootstrapCacheProvider =
new SharedResolutionCacheAdapter(BOOTSTRAP_HASH, null, sharedResolutionCache);
@Override
public final TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoader) {
if (classLoader == null) {
return createCachingTypePool(bootstrapCacheProvider, classFileLocator);
}
WeakReference loaderRef =
loaderRefCache.computeIfAbsent(classLoader, WeakReference::new);
int loaderHash = classLoader.hashCode();
return createCachingTypePool(loaderHash, loaderRef, classFileLocator);
}
@Override
public final TypePool typePool(
ClassFileLocator classFileLocator, ClassLoader classLoader, String name) {
return typePool(classFileLocator, classLoader);
}
private TypePool.CacheProvider createCacheProvider(
int loaderHash, WeakReference loaderRef) {
return new SharedResolutionCacheAdapter(loaderHash, loaderRef, sharedResolutionCache);
}
private TypePool createCachingTypePool(
int loaderHash, WeakReference loaderRef, ClassFileLocator classFileLocator) {
return new TypePool.Default.WithLazyResolution(
createCacheProvider(loaderHash, loaderRef),
classFileLocator,
TypePool.Default.ReaderMode.FAST);
}
private static TypePool createCachingTypePool(
TypePool.CacheProvider cacheProvider, ClassFileLocator classFileLocator) {
return new TypePool.Default.WithLazyResolution(
cacheProvider, classFileLocator, TypePool.Default.ReaderMode.FAST);
}
/**
* TypeCacheKey is key for the sharedResolutionCache. Conceptually, it is a mix of ClassLoader &
* class name.
*
* For efficiency & GC purposes, it is actually composed of loaderHash &
* WeakReference<ClassLoader>
*
*
The loaderHash exists to avoid calling get & strengthening the Reference.
*/
static final class TypeCacheKey {
private final int loaderHash;
private final WeakReference loaderRef;
private final String className;
private final int hashCode;
TypeCacheKey(int loaderHash, WeakReference loaderRef, String className) {
this.loaderHash = loaderHash;
this.loaderRef = loaderRef;
this.className = className;
hashCode = 31 * loaderHash + className.hashCode();
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof TypeCacheKey)) {
return false;
}
TypeCacheKey other = (TypeCacheKey) obj;
if (loaderHash != other.loaderHash) {
return false;
}
if (!className.equals(other.className)) {
return false;
}
// Fastpath loaderRef equivalence -- works because of WeakReference cache used
// Also covers the bootstrap null loaderRef case
if (loaderRef == other.loaderRef) {
return true;
}
// need to perform a deeper loader check -- requires calling Reference.get
// which can strengthen the Reference, so deliberately done last
// If either reference has gone null, they aren't considered equivalent
// Technically, this is a bit of violation of equals semantics, since
// two equivalent references can become not equivalent.
// In this case, it is fine because that means the ClassLoader is no
// longer live, so the entries will never match anyway and will fall
// out of the cache.
ClassLoader thisLoader = loaderRef.get();
if (thisLoader == null) {
return false;
}
ClassLoader otherLoader = other.loaderRef.get();
if (otherLoader == null) {
return false;
}
return thisLoader == otherLoader;
}
@Override
public final int hashCode() {
return hashCode;
}
@Override
public String toString() {
return "TypeCacheKey{"
+ "loaderHash="
+ loaderHash
+ ", loaderRef="
+ loaderRef
+ ", className='"
+ className
+ '\''
+ '}';
}
}
static final class SharedResolutionCacheAdapter implements TypePool.CacheProvider {
private static final String OBJECT_NAME = "java.lang.Object";
private static final TypePool.Resolution OBJECT_RESOLUTION =
new TypePool.Resolution.Simple(new CachingTypeDescription(TypeDescription.OBJECT));
private final int loaderHash;
private final WeakReference loaderRef;
private final Cache sharedResolutionCache;
SharedResolutionCacheAdapter(
int loaderHash,
WeakReference loaderRef,
Cache sharedResolutionCache) {
this.loaderHash = loaderHash;
this.loaderRef = loaderRef;
this.sharedResolutionCache = sharedResolutionCache;
}
@Override
public TypePool.Resolution find(String className) {
TypePool.Resolution existingResolution =
sharedResolutionCache.get(new TypeCacheKey(loaderHash, loaderRef, className));
if (existingResolution != null) {
return existingResolution;
}
if (OBJECT_NAME.equals(className)) {
return OBJECT_RESOLUTION;
}
return null;
}
@Override
public TypePool.Resolution register(String className, TypePool.Resolution resolution) {
if (OBJECT_NAME.equals(className)) {
return resolution;
}
resolution = new CachingResolution(resolution);
sharedResolutionCache.put(new TypeCacheKey(loaderHash, loaderRef, className), resolution);
return resolution;
}
@Override
public void clear() {
// Allowing the high-level eviction policy make the clearing decisions
}
}
private static class CachingResolution implements TypePool.Resolution {
private final TypePool.Resolution delegate;
private TypeDescription cachedResolution;
public CachingResolution(TypePool.Resolution delegate) {
this.delegate = delegate;
}
@Override
public boolean isResolved() {
return delegate.isResolved();
}
@Override
public TypeDescription resolve() {
// Intentionally not "thread safe". Duplicate work deemed an acceptable trade-off.
if (cachedResolution == null) {
cachedResolution = new CachingTypeDescription(delegate.resolve());
}
return cachedResolution;
}
}
/**
* TypeDescription implementation that delegates and caches the results for the expensive calls
* commonly used by our instrumentation.
*/
private static class CachingTypeDescription
extends TypeDescription.AbstractBase.OfSimpleType.WithDelegation {
private final TypeDescription delegate;
// These fields are intentionally not "thread safe".
// Duplicate work deemed an acceptable trade-off.
private Generic superClass;
private TypeList.Generic interfaces;
private AnnotationList annotations;
private MethodList methods;
public CachingTypeDescription(TypeDescription delegate) {
this.delegate = delegate;
}
@Override
protected TypeDescription delegate() {
return delegate;
}
@Override
public Generic getSuperClass() {
if (superClass == null) {
superClass = delegate.getSuperClass();
}
return superClass;
}
@Override
public TypeList.Generic getInterfaces() {
if (interfaces == null) {
interfaces = delegate.getInterfaces();
}
return interfaces;
}
@Override
public AnnotationList getDeclaredAnnotations() {
if (annotations == null) {
annotations = delegate.getDeclaredAnnotations();
}
return annotations;
}
@Override
public MethodList getDeclaredMethods() {
if (methods == null) {
methods = delegate.getDeclaredMethods();
}
return methods;
}
@Override
public String getName() {
return delegate.getName();
}
}
}