Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 15 additions & 16 deletions utils/src/main/java/datadog/instrument/utils/ClassInfoCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.IntPredicate;

/**
* Shares class information from multiple classloaders in a single cache.
*
* <p>Information is indexed by class-name, with an optional class-loader filter. When multiple
* <p>Information is indexed by class-name, with an optional class-loader matcher. When multiple
* classes are defined with the same name, only one has its information cached at any given time.
* The class-loader can then be used to check information is for the correct class.
*
* @see ClassLoaderIndex#getClassLoaderKeyId
* @see ClassLoaderIndex#getClassLoaderKeyId(ClassLoader)
*/
public final class ClassInfoCache<T> {

Expand Down Expand Up @@ -72,7 +71,7 @@ public void share(String className, T info) {
}

/**
* Finds information for the given class-name, under the given class-loader.
* Finds information for the given class-name, scoped to the given class-loader.
*
* @param className the class-name
* @param cl the class-loader
Expand All @@ -83,18 +82,18 @@ public T find(CharSequence className, ClassLoader cl) {
}

/**
* Shares information for the given class-name, under the given class-loader.
* Shares information for the given class-name, scoped to the given class-loader.
*
* @param className the class-name
* @param info the information to share under the class-name and class-loader
* @param cl limit the information to this class-loader
* @param cl scope the information to this class-loader
*/
public void share(String className, T info, ClassLoader cl) {
share(className, info, ClassLoaderIndex.getClassLoaderKeyId(cl));
}

/**
* Finds information for the given class-name, under the given class-loader key.
* Finds information for the given class-name, scoped to the given class-loader key.
*
* @param className the class-name
* @param classLoaderKeyId the class-loader's key-id
Expand All @@ -113,7 +112,7 @@ public T find(CharSequence className, int classLoaderKeyId) {
SharedInfo existing = shared[slot];
if (existing != null) {
if (existing.className.contentEquals(className)) {
// filter on class-loader, -1 on either side matches all
// match on class-loader key, -1 on either side matches all
if ((classLoaderKeyId ^ existing.classLoaderKeyId) <= 0) {
// use global TICKS as a substitute for access time
// TICKS is only incremented in 'share' for performance reasons
Expand All @@ -134,15 +133,15 @@ public T find(CharSequence className, int classLoaderKeyId) {
}

/**
* Finds information for the given class-name, filtered by class-loader key.
* Finds information for the given class-name, scoped to class-loaders with matching keys.
*
* @param className the class-name
* @param classLoaderKeyFilter the filter for the class-loader key
* @return information shared under the class-name and filtered class-loader
* @param classLoaderKeyMatcher matcher of class-loader keys
* @return information shared under the class-name and matching class-loader
* @see ClassLoaderIndex#getClassLoaderKeyId(ClassLoader)
*/
@SuppressWarnings("unchecked")
public T find(CharSequence className, IntPredicate classLoaderKeyFilter) {
public T find(CharSequence className, ClassLoaderKeyMatcher classLoaderKeyMatcher) {
final int hash = className.hashCode();
final SharedInfo[] shared = this.shared;
final int slotMask = this.slotMask;
Expand All @@ -153,9 +152,9 @@ public T find(CharSequence className, IntPredicate classLoaderKeyFilter) {
SharedInfo existing = shared[slot];
if (existing != null) {
if (existing.className.contentEquals(className)) {
// apply filter to class-loader key, -1 always matches
// apply custom matcher to class-loader key, -1 always matches
if (existing.classLoaderKeyId < 0
|| classLoaderKeyFilter.test(existing.classLoaderKeyId)) {
|| classLoaderKeyMatcher.test(existing.classLoaderKeyId)) {
// use global TICKS as a substitute for access time
// TICKS is only incremented in 'share' for performance reasons
existing.accessed = TICKS.get();
Expand All @@ -175,11 +174,11 @@ public T find(CharSequence className, IntPredicate classLoaderKeyFilter) {
}

/**
* Shares information for the given class-name, under the given class-loader key.
* Shares information for the given class-name, scoped to the given class-loader key.
*
* @param className the class-name
* @param info the information to share under the class-name and class-loader
* @param classLoaderKeyId limit the information to this class-loader key-id
* @param classLoaderKeyId scope the information to this class-loader key-id
* @see ClassLoaderIndex#getClassLoaderKeyId(ClassLoader)
*/
@SuppressWarnings("StatementWithEmptyBody")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2025-Present Datadog, Inc.
*/

package datadog.instrument.utils;

/**
* Matcher used to partition class-loaders by their indexed key-id.
*
* @see ClassLoaderIndex#getClassLoaderKeyId(ClassLoader)
* @see ClassInfoCache#find(CharSequence, ClassLoaderKeyMatcher)
*/
@FunctionalInterface
public interface ClassLoaderKeyMatcher {

/**
* Evaluates this matcher against the given class-loader key-id.
*
* @param classLoaderKeyId the class-loader key-id
* @return {@code true} if the key-id matched; otherwise {@code false}
*/
boolean test(int classLoaderKeyId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import java.util.HashSet;
import java.util.Set;
import java.util.function.IntPredicate;
import org.junit.jupiter.api.Test;

class ClassInfoCacheTest {
Expand All @@ -20,10 +19,10 @@ void basicOperation() {
ClassLoader myCL = newCL();
ClassLoader notMyCL = newCL();

int myCLKey = ClassLoaderIndex.getClassLoaderKeyId(myCL);
int myCLKeyId = ClassLoaderIndex.getClassLoaderKeyId(myCL);

IntPredicate myCLFilter = sameCLKey(myCLKey);
IntPredicate notMyCLFilter = myCLFilter.negate();
ClassLoaderKeyMatcher matchMyCL = sameCLKey(myCLKeyId);
ClassLoaderKeyMatcher dontMatchMyCL = keyId -> !matchMyCL.test(keyId);

assertNull(cache.find("example.test.MyGlobalClass"));
assertNull(cache.find("example.test.MyLocalClass"));
Expand All @@ -37,13 +36,13 @@ void basicOperation() {
assertNull(cache.find("example.test.MyLocalClass", notMyCL));
assertNull(cache.find("example.test.NotMyClass", notMyCL));

assertNull(cache.find("example.test.MyGlobalClass", myCLFilter));
assertNull(cache.find("example.test.MyLocalClass", myCLFilter));
assertNull(cache.find("example.test.NotMyClass", myCLFilter));
assertNull(cache.find("example.test.MyGlobalClass", matchMyCL));
assertNull(cache.find("example.test.MyLocalClass", matchMyCL));
assertNull(cache.find("example.test.NotMyClass", matchMyCL));

assertNull(cache.find("example.test.MyGlobalClass", notMyCLFilter));
assertNull(cache.find("example.test.MyLocalClass", notMyCLFilter));
assertNull(cache.find("example.test.NotMyClass", notMyCLFilter));
assertNull(cache.find("example.test.MyGlobalClass", dontMatchMyCL));
assertNull(cache.find("example.test.MyLocalClass", dontMatchMyCL));
assertNull(cache.find("example.test.NotMyClass", dontMatchMyCL));

cache.share("example.test.MyGlobalClass", "my global data");
cache.share("example.test.MyLocalClass", "my local data", myCL);
Expand All @@ -60,13 +59,13 @@ void basicOperation() {
assertNull(cache.find("example.test.MyLocalClass", notMyCL));
assertNull(cache.find("example.test.NotMyClass", notMyCL));

assertEquals("my global data", cache.find("example.test.MyGlobalClass", myCLFilter));
assertEquals("my local data", cache.find("example.test.MyLocalClass", myCLFilter));
assertNull(cache.find("example.test.NotMyClass", myCLFilter));
assertEquals("my global data", cache.find("example.test.MyGlobalClass", matchMyCL));
assertEquals("my local data", cache.find("example.test.MyLocalClass", matchMyCL));
assertNull(cache.find("example.test.NotMyClass", matchMyCL));

assertEquals("my global data", cache.find("example.test.MyGlobalClass", notMyCLFilter));
assertNull(cache.find("example.test.MyLocalClass", notMyCLFilter));
assertNull(cache.find("example.test.NotMyClass", notMyCLFilter));
assertEquals("my global data", cache.find("example.test.MyGlobalClass", dontMatchMyCL));
assertNull(cache.find("example.test.MyLocalClass", dontMatchMyCL));
assertNull(cache.find("example.test.NotMyClass", dontMatchMyCL));

cache.clear();

Expand All @@ -82,13 +81,13 @@ void basicOperation() {
assertNull(cache.find("example.test.MyLocalClass", notMyCL));
assertNull(cache.find("example.test.NotMyClass", notMyCL));

assertNull(cache.find("example.test.MyGlobalClass", myCLFilter));
assertNull(cache.find("example.test.MyLocalClass", myCLFilter));
assertNull(cache.find("example.test.NotMyClass", myCLFilter));
assertNull(cache.find("example.test.MyGlobalClass", matchMyCL));
assertNull(cache.find("example.test.MyLocalClass", matchMyCL));
assertNull(cache.find("example.test.NotMyClass", matchMyCL));

assertNull(cache.find("example.test.MyGlobalClass", notMyCLFilter));
assertNull(cache.find("example.test.MyLocalClass", notMyCLFilter));
assertNull(cache.find("example.test.NotMyClass", notMyCLFilter));
assertNull(cache.find("example.test.MyGlobalClass", dontMatchMyCL));
assertNull(cache.find("example.test.MyLocalClass", dontMatchMyCL));
assertNull(cache.find("example.test.NotMyClass", dontMatchMyCL));
}

@Test
Expand Down Expand Up @@ -151,8 +150,8 @@ void overflow() {
}
}

private static IntPredicate sameCLKey(int clKey) {
return k -> k == clKey;
private static ClassLoaderKeyMatcher sameCLKey(int expectedKeyId) {
return keyId -> keyId == expectedKeyId;
}

private static ClassLoader newCL() {
Expand Down