From 99b42e13ba57dfa408b17d2103bd3c13079255d7 Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Thu, 18 Nov 2021 11:17:40 +0200 Subject: [PATCH 01/30] Drop instrumentation-api-caching module and move weak cache implementation to instrumentation-api --- .../otel.javaagent-bootstrap.gradle.kts | 2 +- .../otel.javaagent-instrumentation.gradle.kts | 2 +- .../otel.library-instrumentation.gradle.kts | 2 +- .../build.gradle.kts | 4 +- .../api/annotation/support/MethodCache.java | 2 +- .../MethodSpanAttributesExtractor.java | 2 +- .../instrumentation/api/caching/Cache.java | 2 +- instrumentation-api/build.gradle.kts | 8 +--- .../instrumentation/api/cache/Cache.java | 41 +++++++++++++++++++ .../api/cache/CacheBuilder.java | 16 ++++++++ .../api/cache}/WeakLockFreeCache.java | 2 +- .../api/db/SqlStatementSanitizer.java | 7 ++-- .../http/HttpHeaderAttributes.java | 10 ++--- .../internal/RuntimeVirtualFieldSupplier.java | 4 +- .../api/caching/CacheTest.java | 2 + .../netty/common/FutureListenerWrappers.java | 5 ++- .../build.gradle.kts | 2 +- javaagent-bootstrap/build.gradle.kts | 2 +- .../javaagent/bootstrap/HelperResources.java | 4 +- javaagent-extension-api/build.gradle.kts | 2 +- .../javaagent/extension/AgentListener.java | 2 +- .../ClassLoaderHasClassesNamedMatcher.java | 5 ++- .../build.gradle.kts | 2 +- .../ClassLoaderMatcherCacheHolder.java | 2 +- javaagent-tooling/build.gradle.kts | 2 +- .../VirtualFieldImplementationsGenerator.java | 4 +- .../ignore/IgnoredClassLoadersMatcher.java | 4 +- .../InstrumentationModuleInstaller.java | 4 +- javaagent/build.gradle.kts | 2 +- muzzle/build.gradle.kts | 2 +- .../javaagent/tooling/HelperInjector.java | 8 ++-- .../muzzle/AgentCachingPoolStrategy.java | 7 ++-- settings.gradle.kts | 6 +-- 33 files changed, 115 insertions(+), 56 deletions(-) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/CacheBuilder.java rename {instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching => instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache}/WeakLockFreeCache.java (96%) diff --git a/conventions/src/main/kotlin/otel.javaagent-bootstrap.gradle.kts b/conventions/src/main/kotlin/otel.javaagent-bootstrap.gradle.kts index 6424e717f2ea..2d6f428fe2fa 100644 --- a/conventions/src/main/kotlin/otel.javaagent-bootstrap.gradle.kts +++ b/conventions/src/main/kotlin/otel.javaagent-bootstrap.gradle.kts @@ -15,5 +15,5 @@ dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) } diff --git a/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts b/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts index 5c29ed7688a2..2538772bed33 100644 --- a/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts +++ b/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts @@ -12,5 +12,5 @@ base.archivesName.set(projectDir.parentFile.name) dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) } diff --git a/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts b/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts index d885cdbb3494..234018583cb5 100644 --- a/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts +++ b/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts @@ -13,5 +13,5 @@ base.archivesName.set(projectDir.parentFile.name) dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) } diff --git a/instrumentation-api-annotation-support/build.gradle.kts b/instrumentation-api-annotation-support/build.gradle.kts index 446635d9de33..3e6ee1a4e564 100644 --- a/instrumentation-api-annotation-support/build.gradle.kts +++ b/instrumentation-api-annotation-support/build.gradle.kts @@ -12,8 +12,8 @@ dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) - testCompileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) +// testCompileOnly(project(":instrumentation-api-caching")) api("io.opentelemetry:opentelemetry-api") api("io.opentelemetry:opentelemetry-semconv") diff --git a/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCache.java b/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCache.java index 33a8975d4914..3b5398ddaabc 100644 --- a/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCache.java +++ b/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCache.java @@ -5,11 +5,11 @@ package io.opentelemetry.instrumentation.api.annotation.support; -import io.opentelemetry.instrumentation.api.caching.Cache; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import io.opentelemetry.instrumentation.api.cache.Cache; /** * Implementation of {@link Cache} that uses {@link ClassValue} to store values keyed by {@link diff --git a/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractor.java b/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractor.java index d8e8cb6fc708..0d8eac89f081 100644 --- a/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractor.java +++ b/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractor.java @@ -6,7 +6,7 @@ package io.opentelemetry.instrumentation.api.annotation.support; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.tracer.AttributeSetter; import java.lang.reflect.Method; diff --git a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Cache.java b/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Cache.java index 4f340ca12aad..549144dce4e3 100644 --- a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Cache.java +++ b/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Cache.java @@ -9,7 +9,7 @@ import javax.annotation.Nullable; /** A cache from keys to values. */ -public interface Cache { +interface Cache { /** Returns a new {@link CacheBuilder} to configure a {@link Cache}. */ static CacheBuilder builder() { diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts index 155fee8acd9c..b201248400bd 100644 --- a/instrumentation-api/build.gradle.kts +++ b/instrumentation-api/build.gradle.kts @@ -14,27 +14,23 @@ sourceSets { // set to generate into. By default it would be the src/main directory itself. srcDir("$buildDir/generated/sources/jflex") } - - val cachingShadedDeps = project(":instrumentation-api-caching") - output.dir(cachingShadedDeps.file("build/extracted/shadow"), "builtBy" to ":instrumentation-api-caching:extractShadowJar") } } group = "io.opentelemetry.instrumentation" dependencies { - compileOnly(project(":instrumentation-api-caching")) - api("io.opentelemetry:opentelemetry-api") api("io.opentelemetry:opentelemetry-semconv") implementation("io.opentelemetry:opentelemetry-api-metrics") implementation("org.slf4j:slf4j-api") + implementation("com.blogspot.mydailyjava:weak-lock-free") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") - testCompileOnly(project(":instrumentation-api-caching")) +// testCompileOnly(project(":instrumentation-api-caching")) testImplementation(project(":testing-common")) testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java new file mode 100644 index 000000000000..e1a129b70077 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache; + +import java.util.function.Function; +import javax.annotation.Nullable; + +/** A cache from keys to values. + * + * Keys are always referenced weakly and are compared using identity comparison, not + * {@link Object#equals(Object)}. + */ +public interface Cache { + + /** Returns a new {@link CacheBuilder} to configure a {@link Cache}. */ + static CacheBuilder builder() { + return new CacheBuilder(); + } + + /** + * Returns the cached value associated with the provided {@code key}. If no value is cached yet, + * computes the value using {@code mappingFunction}, stores the result, and returns it. + */ + V computeIfAbsent(K key, Function mappingFunction); + + /** + * Returns the cached value associated with the provided {@code key} if present, or {@code null} + * otherwise. + */ + @Nullable + V get(K key); + + /** Puts the {@code value} into the cache for the {@code key}. */ + void put(K key, V value); + + /** Removes a value for {@code key} if present. */ + void remove(K key); +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/CacheBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/CacheBuilder.java new file mode 100644 index 000000000000..4efbeb604754 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/CacheBuilder.java @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache; + +/** A builder of {@link Cache}. */ +public final class CacheBuilder { + /** Returns a new {@link Cache} with the settings of this {@link CacheBuilder}. */ + public Cache build() { + return new WeakLockFreeCache<>(); + } + + CacheBuilder() {} +} diff --git a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/WeakLockFreeCache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/WeakLockFreeCache.java similarity index 96% rename from instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/WeakLockFreeCache.java rename to instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/WeakLockFreeCache.java index 692177a9c325..9576bab295e3 100644 --- a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/WeakLockFreeCache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/WeakLockFreeCache.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.api.caching; +package io.opentelemetry.instrumentation.api.cache; import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap; import java.util.function.Function; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java index 29c14c26f5a8..8244779ed5d1 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java @@ -8,8 +8,8 @@ import static io.opentelemetry.instrumentation.api.db.StatementSanitizationConfig.isStatementSanitizationEnabled; import static io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics.CounterNames.SQL_STATEMENT_SANITIZER_CACHE_MISS; +import java.util.concurrent.ConcurrentHashMap; import com.google.auto.value.AutoValue; -import io.opentelemetry.instrumentation.api.caching.Cache; import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; import javax.annotation.Nullable; @@ -20,8 +20,9 @@ public final class SqlStatementSanitizer { private static final SupportabilityMetrics supportability = SupportabilityMetrics.instance(); - private static final Cache sqlToStatementInfoCache = - Cache.builder().setMaximumSize(1000).build(); + //TODO use CHLM + private static final ConcurrentHashMap sqlToStatementInfoCache = + new ConcurrentHashMap<>(); public static SqlStatementInfo sanitize(@Nullable String statement) { return sanitize(statement, SqlDialect.DEFAULT); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java index d67ed8fe8e1a..444fa80f28b8 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java @@ -6,15 +6,15 @@ package io.opentelemetry.instrumentation.api.instrumenter.http; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.instrumentation.api.caching.Cache; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; final class HttpHeaderAttributes { - private static final Cache>> requestKeysCache = - Cache.builder().setMaximumSize(32).build(); - private static final Cache>> responseKeysCache = - Cache.builder().setMaximumSize(32).build(); + private static final ConcurrentHashMap>> requestKeysCache = + new ConcurrentHashMap<>(); + private static final ConcurrentHashMap>> responseKeysCache = + new ConcurrentHashMap<>(); static AttributeKey> requestAttributeKey(String headerName) { return requestKeysCache.computeIfAbsent(headerName, n -> createKey("request", n)); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java index 3e87a2f7a8c1..89a9d42cd46a 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java @@ -5,7 +5,7 @@ package io.opentelemetry.instrumentation.api.internal; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.instrumentation.api.field.VirtualField; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -55,7 +55,7 @@ public VirtualField find(Class type, Class field } private static final class CacheBasedVirtualField extends VirtualField { - private final Cache cache = Cache.builder().setWeakKeys().build(); + private final Cache cache = Cache.builder().build(); @Override @Nullable diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/CacheTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/CacheTest.java index 86898157d1c3..3d8615b7fe4f 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/CacheTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/CacheTest.java @@ -11,6 +11,8 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import io.opentelemetry.instrumentation.api.cache.WeakLockFreeCache; + class CacheTest { @Nested diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java index bcf615426187..8d47c4fcde93 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java @@ -11,7 +11,7 @@ import io.netty.util.concurrent.ProgressiveFuture; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; public final class FutureListenerWrappers { // Instead of VirtualField use Cache with weak keys and weak values to store link between original @@ -22,9 +22,10 @@ public final class FutureListenerWrappers { // Also note that it's ok if the value is collected prior to the key, since this cache is only // used to remove the wrapped listener from the netty future, and if the value is collected prior // to the key, that means it's no longer used (referenced) by the netty future anyways. + //TODO .setWeakValues() private static final Cache< GenericFutureListener>, GenericFutureListener>> - wrappers = Cache.builder().setWeakKeys().setWeakValues().build(); + wrappers = Cache.builder().build(); private static final ClassValue shouldWrap = new ClassValue() { diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 588a5aed9ef8..ea2db8c7a3e5 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) } tasks.compileTestJava { diff --git a/javaagent-bootstrap/build.gradle.kts b/javaagent-bootstrap/build.gradle.kts index da07d66ec13e..f84a41f5f0d8 100644 --- a/javaagent-bootstrap/build.gradle.kts +++ b/javaagent-bootstrap/build.gradle.kts @@ -15,5 +15,5 @@ dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) } diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java index 6c79b71f21db..80ab4173865b 100644 --- a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java @@ -5,10 +5,10 @@ package io.opentelemetry.javaagent.bootstrap; -import io.opentelemetry.instrumentation.api.caching.Cache; import java.net.URL; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import io.opentelemetry.instrumentation.api.cache.Cache; /** * A holder of resources needed by instrumentation. We store them in the bootstrap classloader so @@ -18,7 +18,7 @@ public final class HelperResources { private static final Cache> RESOURCES = - Cache.builder().setWeakKeys().build(); + Cache.builder().build(); /** Registers the {@code payload} to be available to instrumentation at {@code path}. */ public static void register(ClassLoader classLoader, String path, URL url) { diff --git a/javaagent-extension-api/build.gradle.kts b/javaagent-extension-api/build.gradle.kts index 0ac62d41639e..47c6c8c164ac 100644 --- a/javaagent-extension-api/build.gradle.kts +++ b/javaagent-extension-api/build.gradle.kts @@ -19,5 +19,5 @@ dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) } diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/AgentListener.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/AgentListener.java index c2256124ff42..8c72076d436a 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/AgentListener.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/AgentListener.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.extension; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.instrumentation.api.config.Config; import io.opentelemetry.instrumentation.api.field.VirtualField; import java.lang.instrument.Instrumentation; diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java index 2037afe51d54..a1035c549204 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java @@ -5,15 +5,16 @@ package io.opentelemetry.javaagent.extension.matcher; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.javaagent.instrumentation.api.internal.ClassLoaderMatcherCacheHolder; import io.opentelemetry.javaagent.instrumentation.api.internal.InClassLoaderMatcher; import net.bytebuddy.matcher.ElementMatcher; class ClassLoaderHasClassesNamedMatcher extends ElementMatcher.Junction.AbstractBase { + //TODO .setMaximumSize(25) private final Cache cache = - Cache.builder().setWeakKeys().setMaximumSize(25).build(); + Cache.builder().build(); private final String[] resources; diff --git a/javaagent-instrumentation-api/build.gradle.kts b/javaagent-instrumentation-api/build.gradle.kts index ca4f0114f13a..e12f2a298e60 100644 --- a/javaagent-instrumentation-api/build.gradle.kts +++ b/javaagent-instrumentation-api/build.gradle.kts @@ -22,5 +22,5 @@ dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) } diff --git a/javaagent-instrumentation-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/internal/ClassLoaderMatcherCacheHolder.java b/javaagent-instrumentation-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/internal/ClassLoaderMatcherCacheHolder.java index fbaecc2985e1..4fc62cfbc721 100644 --- a/javaagent-instrumentation-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/internal/ClassLoaderMatcherCacheHolder.java +++ b/javaagent-instrumentation-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/internal/ClassLoaderMatcherCacheHolder.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.api.internal; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.instrumentation.api.internal.GuardedBy; import java.net.URL; import java.util.ArrayList; diff --git a/javaagent-tooling/build.gradle.kts b/javaagent-tooling/build.gradle.kts index 82241142daf1..20fbc55225da 100644 --- a/javaagent-tooling/build.gradle.kts +++ b/javaagent-tooling/build.gradle.kts @@ -43,7 +43,7 @@ dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) } // Here we only include autoconfigure but don"t include OTLP exporters to ensure they are only in diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/VirtualFieldImplementationsGenerator.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/VirtualFieldImplementationsGenerator.java index 07695d59a1cb..083b6ec8f529 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/VirtualFieldImplementationsGenerator.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/VirtualFieldImplementationsGenerator.java @@ -9,7 +9,7 @@ import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealSetterName; import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getVirtualFieldImplementationClassName; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.instrumentation.api.field.VirtualField; import io.opentelemetry.javaagent.tooling.Utils; import io.opentelemetry.javaagent.tooling.muzzle.VirtualFieldMappings; @@ -264,7 +264,7 @@ private MethodVisitor getMethodVisitor(String methodName) { @SuppressWarnings({"UnusedMethod", "UnusedVariable", "MethodCanBeStatic"}) static final class VirtualFieldImplementationTemplate extends VirtualField { private static final VirtualFieldImplementationTemplate INSTANCE = - new VirtualFieldImplementationTemplate(Cache.builder().setWeakKeys().build()); + new VirtualFieldImplementationTemplate(Cache.builder().build()); private final Cache map; diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java index 54ee03f7f631..e10da74c2afe 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.tooling.ignore; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.javaagent.bootstrap.PatchLogger; import io.opentelemetry.javaagent.tooling.util.Trie; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; @@ -18,7 +18,7 @@ public class IgnoredClassLoadersMatcher extends ElementMatcher.Junction.Abstract /* Cache of classloader-instance -> (true|false). True = skip instrumentation. False = safe to instrument. */ private static final Cache skipCache = - Cache.builder().setWeakKeys().build(); + Cache.builder().build(); private final Trie ignoredClassLoaders; diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java index b67b5e4b26ea..2ecd4e33bb37 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java @@ -10,7 +10,7 @@ import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.not; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.tooling.HelperInjector; @@ -125,7 +125,7 @@ AgentBuilder install( private static class MuzzleMatcher implements AgentBuilder.RawMatcher { private final InstrumentationModule instrumentationModule; private final AtomicBoolean initialized = new AtomicBoolean(false); - private final Cache matchCache = Cache.builder().setWeakKeys().build(); + private final Cache matchCache = Cache.builder().build(); private volatile ReferenceMatcher referenceMatcher; private MuzzleMatcher(InstrumentationModule instrumentationModule) { diff --git a/javaagent/build.gradle.kts b/javaagent/build.gradle.kts index f6bd97ea1fd5..f40bbb6b1374 100644 --- a/javaagent/build.gradle.kts +++ b/javaagent/build.gradle.kts @@ -60,7 +60,7 @@ val caffeine3Version: String by project dependencies { bootstrapLibs(project(":instrumentation-api")) bootstrapLibs(project(":instrumentation-api-annotation-support")) - bootstrapLibs(project(":instrumentation-api-caching:caffeine3", configuration = "shadow")) +// bootstrapLibs(project(":instrumentation-api-caching:caffeine3", configuration = "shadow")) bootstrapLibs(project(":javaagent-bootstrap")) bootstrapLibs(project(":javaagent-instrumentation-api")) bootstrapLibs("org.slf4j:slf4j-simple") diff --git a/muzzle/build.gradle.kts b/muzzle/build.gradle.kts index f0aa44b9a561..fe1799257469 100644 --- a/muzzle/build.gradle.kts +++ b/muzzle/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { // this only exists to make Intellij happy since it doesn't (currently at least) understand our // inclusion of this artifact inside of :instrumentation-api - compileOnly(project(":instrumentation-api-caching")) +// compileOnly(project(":instrumentation-api-caching")) testImplementation(project(":testing-common")) testImplementation("com.google.guava:guava") diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java index 37e89e4ef78d..00b446a34885 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.tooling; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.javaagent.bootstrap.HelperResources; import io.opentelemetry.javaagent.tooling.muzzle.HelperResource; import java.io.File; @@ -59,7 +59,7 @@ public String toString() { }; private static final Cache, Boolean> injectedClasses = - Cache.builder().setWeakKeys().build(); + Cache.builder().build(); private final String requestingName; @@ -70,9 +70,9 @@ public String toString() { private final Map dynamicTypeMap = new LinkedHashMap<>(); private final Cache injectedClassLoaders = - Cache.builder().setWeakKeys().build(); + Cache.builder().build(); private final Cache resourcesInjectedClassLoaders = - Cache.builder().setWeakKeys().build(); + Cache.builder().build(); private final List> helperModules = new CopyOnWriteArrayList<>(); diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java index 7bf1c463a70e..d25d127f4d91 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java @@ -5,8 +5,8 @@ package io.opentelemetry.javaagent.tooling.muzzle; -import io.opentelemetry.instrumentation.api.caching.Cache; import java.lang.ref.WeakReference; +import io.opentelemetry.instrumentation.api.cache.Cache; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.description.annotation.AnnotationList; import net.bytebuddy.description.method.MethodDescription; @@ -49,13 +49,14 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy { * */ final Cache> loaderRefCache = - Cache.builder().setWeakKeys().build(); + Cache.builder().build(); /** * Single shared Type.Resolution cache -- uses a composite key -- conceptually of loader & name */ + //TODO .setMaximumSize(TYPE_CAPACITY) final Cache sharedResolutionCache = - Cache.builder().setMaximumSize(TYPE_CAPACITY).build(); + Cache.builder().build(); // fast path for bootstrap final SharedResolutionCacheAdapter bootstrapCacheProvider = diff --git a/settings.gradle.kts b/settings.gradle.kts index 34ba2a2df708..4cd6970d737a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -67,9 +67,9 @@ include(":javaagent") include(":bom-alpha") include(":instrumentation-api") -include(":instrumentation-api-caching") -include(":instrumentation-api-caching:caffeine2") -include(":instrumentation-api-caching:caffeine3") +//include(":instrumentation-api-caching") +//include(":instrumentation-api-caching:caffeine2") +//include(":instrumentation-api-caching:caffeine3") include(":javaagent-instrumentation-api") include(":instrumentation-api-annotation-support") From f08d4f59048eb26ec58e4de7b174c9835d638dec Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Thu, 18 Nov 2021 12:50:40 +0200 Subject: [PATCH 02/30] Some test fixes --- .../MethodSpanAttributesExtractorTest.groovy | 2 +- .../annotation/support/MethodCacheTest.java | 2 +- .../instrumentation/api/cache/CacheTest.java | 135 +++++++++++++++++ .../api/cache/PatchCaffeineTest.java | 32 ++++ .../api/caching/CacheTest.java | 137 ------------------ .../api/caching/PatchCaffeineTest.java | 38 ----- 6 files changed, 169 insertions(+), 177 deletions(-) create mode 100644 instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java create mode 100644 instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java delete mode 100644 instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/CacheTest.java delete mode 100644 instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/PatchCaffeineTest.java diff --git a/instrumentation-api-annotation-support/src/test/groovy/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractorTest.groovy b/instrumentation-api-annotation-support/src/test/groovy/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractorTest.groovy index 9cf249ad30d1..20712b824f79 100644 --- a/instrumentation-api-annotation-support/src/test/groovy/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractorTest.groovy +++ b/instrumentation-api-annotation-support/src/test/groovy/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractorTest.groovy @@ -6,7 +6,7 @@ package io.opentelemetry.instrumentation.api.annotation.support import io.opentelemetry.api.common.AttributesBuilder -import io.opentelemetry.instrumentation.api.caching.Cache +import io.opentelemetry.instrumentation.api.cache.Cache import spock.lang.Specification import java.lang.reflect.Method diff --git a/instrumentation-api-annotation-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCacheTest.java b/instrumentation-api-annotation-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCacheTest.java index 9521f709fe88..42aed3fd3636 100644 --- a/instrumentation-api-annotation-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCacheTest.java +++ b/instrumentation-api-annotation-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCacheTest.java @@ -11,13 +11,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import io.opentelemetry.instrumentation.api.caching.Cache; import java.lang.reflect.Method; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import io.opentelemetry.instrumentation.api.cache.Cache; @ExtendWith(MockitoExtension.class) public class MethodCacheTest { diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java new file mode 100644 index 000000000000..69c45c85e8a4 --- /dev/null +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class CacheTest { + +// @Nested +// @SuppressWarnings("ClassCanBeStatic") +// class StrongKeys { +// @Test +// void unbounded() { +// Cache cache = Cache.builder().build(); +// +// assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); +// cache.remove("bear"); +// +// CaffeineCache caffeineCache = ((CaffeineCache) cache); +// assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); +// assertThat(caffeineCache.keySet()).hasSize(1); +// +// assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); +// assertThat(caffeineCache.keySet()).hasSize(1); +// +// cache.put("dog", "bark"); +// assertThat(cache.get("dog")).isEqualTo("bark"); +// assertThat(cache.get("cat")).isEqualTo("meow"); +// assertThat(cache.get("bear")).isNull(); +// assertThat(caffeineCache.keySet()).hasSize(2); +// assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); +// } +// +// @Test +// void bounded() { +// Cache cache = Cache.builder().setMaximumSize(1).build(); +// +// assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); +// cache.remove("bear"); +// +// CaffeineCache caffeineCache = ((CaffeineCache) cache); +// assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); +// assertThat(caffeineCache.keySet()).hasSize(1); +// +// assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); +// assertThat(caffeineCache.keySet()).hasSize(1); +// +// cache.put("dog", "bark"); +// assertThat(cache.get("dog")).isEqualTo("bark"); +// caffeineCache.cleanup(); +// assertThat(caffeineCache.keySet()).hasSize(1); +// assertThat(cache.computeIfAbsent("cat", unused -> "purr")).isEqualTo("purr"); +// } +// } + + @Nested + @SuppressWarnings("ClassCanBeStatic") + class WeakKeys { + @Test + void unbounded() { + Cache cache = Cache.builder().build(); + + assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); + cache.remove("bear"); + + WeakLockFreeCache weakLockFreeCache = ((WeakLockFreeCache) cache); + String cat = new String("cat"); + String dog = new String("dog"); + assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); + assertThat(weakLockFreeCache.size()).isEqualTo(1); + + assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow"); + assertThat(weakLockFreeCache.size()).isEqualTo(1); + + cache.put(dog, "bark"); + assertThat(cache.get(dog)).isEqualTo("bark"); + assertThat(cache.get(cat)).isEqualTo("meow"); + assertThat(cache.get(new String("dog"))).isNull(); + assertThat(weakLockFreeCache.size()).isEqualTo(2); + assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); + + cat = null; + System.gc(); + // Wait for GC to be reflected. + await().untilAsserted(() -> assertThat(weakLockFreeCache.size()).isEqualTo(1)); + assertThat(cache.computeIfAbsent(dog, unused -> "bark")).isEqualTo("bark"); + dog = null; + System.gc(); + // Wait for GC to be reflected. + await().untilAsserted(() -> assertThat(weakLockFreeCache.size()).isEqualTo(0)); + } + +// @Test +// void bounded() { +// io.opentelemetry.instrumentation.api.caching.Cache cache = io.opentelemetry.instrumentation.api.caching.Cache.builder().setWeakKeys().setMaximumSize(1).build(); +// +// assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); +// cache.remove("bear"); +// +// CaffeineCache caffeineCache = ((CaffeineCache) cache); +// +// String cat = new String("cat"); +// String dog = new String("dog"); +// assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); +// assertThat(cache.get(cat)).isEqualTo("meow"); +// assertThat(cache.get(new String("cat"))).isNull(); +// assertThat(caffeineCache.keySet()).hasSize(1); +// +// assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow"); +// assertThat(caffeineCache.keySet()).hasSize(1); +// +// cache.put(dog, "bark"); +// assertThat(cache.get(dog)).isEqualTo("bark"); +// assertThat(cache.get(new String("dog"))).isNull(); +// caffeineCache.cleanup(); +// assertThat(caffeineCache.keySet()).hasSize(1); +// dog = null; +// System.gc(); +// // Wait for GC to be reflected. +// await() +// .untilAsserted( +// () -> { +// caffeineCache.cleanup(); +// assertThat(caffeineCache.keySet()).isEmpty(); +// }); +// } + } +} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java new file mode 100644 index 000000000000..e1520295f784 --- /dev/null +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache; + +class PatchCaffeineTest { + +// @Test +// void cleanupNotForkJoinTask() { +// AtomicReference errorRef = new AtomicReference<>(); +// io.opentelemetry.instrumentation.api.caching.Cache cache = +// io.opentelemetry.instrumentation.api.caching.Cache.builder() +// .setExecutor( +// task -> { +// try { +// assertThat(task).isNotInstanceOf(ForkJoinTask.class); +// } catch (AssertionError e) { +// errorRef.set(e); +// } +// }) +// .setMaximumSize(1) +// .build(); +// assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); +// assertThat(cache.computeIfAbsent("dog", unused -> "bark")).isEqualTo("bark"); +// AssertionError error = errorRef.get(); +// if (error != null) { +// throw error; +// } +// } +} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/CacheTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/CacheTest.java deleted file mode 100644 index 3d8615b7fe4f..000000000000 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/CacheTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.caching; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import io.opentelemetry.instrumentation.api.cache.WeakLockFreeCache; - -class CacheTest { - - @Nested - @SuppressWarnings("ClassCanBeStatic") - class StrongKeys { - @Test - void unbounded() { - Cache cache = Cache.builder().build(); - - assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); - cache.remove("bear"); - - CaffeineCache caffeineCache = ((CaffeineCache) cache); - assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - assertThat(caffeineCache.keySet()).hasSize(1); - - assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); - assertThat(caffeineCache.keySet()).hasSize(1); - - cache.put("dog", "bark"); - assertThat(cache.get("dog")).isEqualTo("bark"); - assertThat(cache.get("cat")).isEqualTo("meow"); - assertThat(cache.get("bear")).isNull(); - assertThat(caffeineCache.keySet()).hasSize(2); - assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - } - - @Test - void bounded() { - Cache cache = Cache.builder().setMaximumSize(1).build(); - - assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); - cache.remove("bear"); - - CaffeineCache caffeineCache = ((CaffeineCache) cache); - assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - assertThat(caffeineCache.keySet()).hasSize(1); - - assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); - assertThat(caffeineCache.keySet()).hasSize(1); - - cache.put("dog", "bark"); - assertThat(cache.get("dog")).isEqualTo("bark"); - caffeineCache.cleanup(); - assertThat(caffeineCache.keySet()).hasSize(1); - assertThat(cache.computeIfAbsent("cat", unused -> "purr")).isEqualTo("purr"); - } - } - - @Nested - @SuppressWarnings("ClassCanBeStatic") - class WeakKeys { - @Test - void unbounded() { - Cache cache = Cache.builder().setWeakKeys().build(); - - assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); - cache.remove("bear"); - - WeakLockFreeCache weakLockFreeCache = ((WeakLockFreeCache) cache); - String cat = new String("cat"); - String dog = new String("dog"); - assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); - assertThat(weakLockFreeCache.size()).isEqualTo(1); - - assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow"); - assertThat(weakLockFreeCache.size()).isEqualTo(1); - - cache.put(dog, "bark"); - assertThat(cache.get(dog)).isEqualTo("bark"); - assertThat(cache.get(cat)).isEqualTo("meow"); - assertThat(cache.get(new String("dog"))).isNull(); - assertThat(weakLockFreeCache.size()).isEqualTo(2); - assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); - - cat = null; - System.gc(); - // Wait for GC to be reflected. - await().untilAsserted(() -> assertThat(weakLockFreeCache.size()).isEqualTo(1)); - assertThat(cache.computeIfAbsent(dog, unused -> "bark")).isEqualTo("bark"); - dog = null; - System.gc(); - // Wait for GC to be reflected. - await().untilAsserted(() -> assertThat(weakLockFreeCache.size()).isEqualTo(0)); - } - - @Test - void bounded() { - Cache cache = Cache.builder().setWeakKeys().setMaximumSize(1).build(); - - assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); - cache.remove("bear"); - - CaffeineCache caffeineCache = ((CaffeineCache) cache); - - String cat = new String("cat"); - String dog = new String("dog"); - assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); - assertThat(cache.get(cat)).isEqualTo("meow"); - assertThat(cache.get(new String("cat"))).isNull(); - assertThat(caffeineCache.keySet()).hasSize(1); - - assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow"); - assertThat(caffeineCache.keySet()).hasSize(1); - - cache.put(dog, "bark"); - assertThat(cache.get(dog)).isEqualTo("bark"); - assertThat(cache.get(new String("dog"))).isNull(); - caffeineCache.cleanup(); - assertThat(caffeineCache.keySet()).hasSize(1); - dog = null; - System.gc(); - // Wait for GC to be reflected. - await() - .untilAsserted( - () -> { - caffeineCache.cleanup(); - assertThat(caffeineCache.keySet()).isEmpty(); - }); - } - } -} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/PatchCaffeineTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/PatchCaffeineTest.java deleted file mode 100644 index 70189fd037ac..000000000000 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/caching/PatchCaffeineTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.caching; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.concurrent.ForkJoinTask; -import java.util.concurrent.atomic.AtomicReference; -import org.junit.jupiter.api.Test; - -class PatchCaffeineTest { - - @Test - void cleanupNotForkJoinTask() { - AtomicReference errorRef = new AtomicReference<>(); - Cache cache = - Cache.builder() - .setExecutor( - task -> { - try { - assertThat(task).isNotInstanceOf(ForkJoinTask.class); - } catch (AssertionError e) { - errorRef.set(e); - } - }) - .setMaximumSize(1) - .build(); - assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - assertThat(cache.computeIfAbsent("dog", unused -> "bark")).isEqualTo("bark"); - AssertionError error = errorRef.get(); - if (error != null) { - throw error; - } - } -} From e651e804444a91b8491cf9fdc3dafa547d759f85 Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Fri, 19 Nov 2021 09:56:28 +0200 Subject: [PATCH 03/30] Some cleanup --- .../src/main/kotlin/otel.javaagent-bootstrap.gradle.kts | 4 ---- .../main/kotlin/otel.javaagent-instrumentation.gradle.kts | 8 +------- .../main/kotlin/otel.library-instrumentation.gradle.kts | 8 +------- instrumentation-api-annotation-support/build.gradle.kts | 5 ----- instrumentation-api/build.gradle.kts | 1 - .../spring/spring-boot-autoconfigure/build.gradle.kts | 4 ---- javaagent-bootstrap/build.gradle.kts | 4 ---- javaagent-extension-api/build.gradle.kts | 4 ---- .../matcher/ClassLoaderHasClassesNamedMatcher.java | 4 +--- javaagent-instrumentation-api/build.gradle.kts | 4 ---- javaagent-tooling/build.gradle.kts | 4 ---- javaagent/build.gradle.kts | 1 - muzzle/build.gradle.kts | 4 ---- .../tooling/muzzle/AgentCachingPoolStrategy.java | 1 + 14 files changed, 4 insertions(+), 52 deletions(-) diff --git a/conventions/src/main/kotlin/otel.javaagent-bootstrap.gradle.kts b/conventions/src/main/kotlin/otel.javaagent-bootstrap.gradle.kts index 2d6f428fe2fa..385efea7386c 100644 --- a/conventions/src/main/kotlin/otel.javaagent-bootstrap.gradle.kts +++ b/conventions/src/main/kotlin/otel.javaagent-bootstrap.gradle.kts @@ -12,8 +12,4 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-api") compileOnly(project(":instrumentation-api")) compileOnly(project(":javaagent-instrumentation-api")) - - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) } diff --git a/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts b/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts index 2538772bed33..c1317df8152c 100644 --- a/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts +++ b/conventions/src/main/kotlin/otel.javaagent-instrumentation.gradle.kts @@ -7,10 +7,4 @@ plugins { extra["mavenGroupId"] = "io.opentelemetry.javaagent.instrumentation" -base.archivesName.set(projectDir.parentFile.name) - -dependencies { - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) -} +base.archivesName.set(projectDir.parentFile.name) \ No newline at end of file diff --git a/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts b/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts index 234018583cb5..8dad1d1cbc1e 100644 --- a/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts +++ b/conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts @@ -8,10 +8,4 @@ plugins { extra["mavenGroupId"] = "io.opentelemetry.instrumentation" -base.archivesName.set(projectDir.parentFile.name) - -dependencies { - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) -} +base.archivesName.set(projectDir.parentFile.name) \ No newline at end of file diff --git a/instrumentation-api-annotation-support/build.gradle.kts b/instrumentation-api-annotation-support/build.gradle.kts index 3e6ee1a4e564..862c84c11fbf 100644 --- a/instrumentation-api-annotation-support/build.gradle.kts +++ b/instrumentation-api-annotation-support/build.gradle.kts @@ -10,11 +10,6 @@ group = "io.opentelemetry.instrumentation" dependencies { implementation(project(":instrumentation-api")) - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) -// testCompileOnly(project(":instrumentation-api-caching")) - api("io.opentelemetry:opentelemetry-api") api("io.opentelemetry:opentelemetry-semconv") diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts index b201248400bd..77d01ca5139a 100644 --- a/instrumentation-api/build.gradle.kts +++ b/instrumentation-api/build.gradle.kts @@ -30,7 +30,6 @@ dependencies { compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") -// testCompileOnly(project(":instrumentation-api-caching")) testImplementation(project(":testing-common")) testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index ea2db8c7a3e5..f8c66a6c01a4 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -52,10 +52,6 @@ dependencies { testImplementation("io.grpc:grpc-api:1.30.2") testImplementation("io.grpc:grpc-netty-shaded:1.30.2") testImplementation(project(":instrumentation-api-annotation-support")) - - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) } tasks.compileTestJava { diff --git a/javaagent-bootstrap/build.gradle.kts b/javaagent-bootstrap/build.gradle.kts index f84a41f5f0d8..ca5af4a7f4eb 100644 --- a/javaagent-bootstrap/build.gradle.kts +++ b/javaagent-bootstrap/build.gradle.kts @@ -12,8 +12,4 @@ dependencies { testImplementation(project(":testing-common")) testImplementation("org.mockito:mockito-core") testImplementation("org.assertj:assertj-core") - - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) } diff --git a/javaagent-extension-api/build.gradle.kts b/javaagent-extension-api/build.gradle.kts index 47c6c8c164ac..905e7590722d 100644 --- a/javaagent-extension-api/build.gradle.kts +++ b/javaagent-extension-api/build.gradle.kts @@ -16,8 +16,4 @@ dependencies { // metrics are unstable, do not expose as api implementation("io.opentelemetry:opentelemetry-sdk-metrics") - - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) } diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java index a1035c549204..2db90a049ea0 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java @@ -12,9 +12,7 @@ class ClassLoaderHasClassesNamedMatcher extends ElementMatcher.Junction.AbstractBase { - //TODO .setMaximumSize(25) - private final Cache cache = - Cache.builder().build(); + private final Cache cache = Cache.builder().build(); private final String[] resources; diff --git a/javaagent-instrumentation-api/build.gradle.kts b/javaagent-instrumentation-api/build.gradle.kts index e12f2a298e60..276a743d3c8f 100644 --- a/javaagent-instrumentation-api/build.gradle.kts +++ b/javaagent-instrumentation-api/build.gradle.kts @@ -19,8 +19,4 @@ dependencies { testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.assertj:assertj-core") - - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) } diff --git a/javaagent-tooling/build.gradle.kts b/javaagent-tooling/build.gradle.kts index 20fbc55225da..b28b533a10c3 100644 --- a/javaagent-tooling/build.gradle.kts +++ b/javaagent-tooling/build.gradle.kts @@ -40,10 +40,6 @@ dependencies { testImplementation("org.assertj:assertj-core") testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-junit-jupiter") - - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) } // Here we only include autoconfigure but don"t include OTLP exporters to ensure they are only in diff --git a/javaagent/build.gradle.kts b/javaagent/build.gradle.kts index f40bbb6b1374..7c423e738c30 100644 --- a/javaagent/build.gradle.kts +++ b/javaagent/build.gradle.kts @@ -60,7 +60,6 @@ val caffeine3Version: String by project dependencies { bootstrapLibs(project(":instrumentation-api")) bootstrapLibs(project(":instrumentation-api-annotation-support")) -// bootstrapLibs(project(":instrumentation-api-caching:caffeine3", configuration = "shadow")) bootstrapLibs(project(":javaagent-bootstrap")) bootstrapLibs(project(":javaagent-instrumentation-api")) bootstrapLibs("org.slf4j:slf4j-simple") diff --git a/muzzle/build.gradle.kts b/muzzle/build.gradle.kts index fe1799257469..8b517c2435df 100644 --- a/muzzle/build.gradle.kts +++ b/muzzle/build.gradle.kts @@ -21,10 +21,6 @@ dependencies { implementation(project(":javaagent-extension-api")) implementation("org.slf4j:slf4j-api") - // this only exists to make Intellij happy since it doesn't (currently at least) understand our - // inclusion of this artifact inside of :instrumentation-api -// compileOnly(project(":instrumentation-api-caching")) - testImplementation(project(":testing-common")) testImplementation("com.google.guava:guava") testImplementation("org.assertj:assertj-core:3.19.0") diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java index d25d127f4d91..647fd70f8d7d 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java @@ -211,6 +211,7 @@ static final class SharedResolutionCacheAdapter implements TypePool.CacheProvide @Override public TypePool.Resolution find(String className) { + //TODO this will not work with WeakLockFreeCache, as it uses instance comparison, not equals TypePool.Resolution existingResolution = sharedResolutionCache.get(new TypeCacheKey(loaderHash, loaderRef, className)); if (existingResolution != null) { From c63d3fe2a0218183121d17f5ca3c4ce302a9834f Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Fri, 19 Nov 2021 10:02:33 +0200 Subject: [PATCH 04/30] Temporary workaround for using weak values in FutureListenerWrappers --- .../netty/common/FutureListenerWrappers.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java index 8d47c4fcde93..bacd3c783a90 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java @@ -12,6 +12,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.instrumentation.api.cache.Cache; +import java.lang.ref.WeakReference; public final class FutureListenerWrappers { // Instead of VirtualField use Cache with weak keys and weak values to store link between original @@ -22,9 +23,8 @@ public final class FutureListenerWrappers { // Also note that it's ok if the value is collected prior to the key, since this cache is only // used to remove the wrapped listener from the netty future, and if the value is collected prior // to the key, that means it's no longer used (referenced) by the netty future anyways. - //TODO .setWeakValues() private static final Cache< - GenericFutureListener>, GenericFutureListener>> + GenericFutureListener>, WeakReference>>> wrappers = Cache.builder().build(); private static final ClassValue shouldWrap = @@ -45,21 +45,29 @@ public static boolean shouldWrap(GenericFutureListener> list @SuppressWarnings("unchecked") public static GenericFutureListener wrap( Context context, GenericFutureListener> delegate) { - return wrappers.computeIfAbsent( + WeakReference>> resultReference = wrappers.computeIfAbsent( delegate, key -> { + GenericFutureListener> wrapper; if (delegate instanceof GenericProgressiveFutureListener) { - return new WrappedProgressiveFutureListener( + wrapper = new WrappedProgressiveFutureListener( context, (GenericProgressiveFutureListener>) delegate); } else { - return new WrappedFutureListener(context, (GenericFutureListener>) delegate); + wrapper = new WrappedFutureListener(context, + (GenericFutureListener>) delegate); } + return new WeakReference<>(wrapper); }); + return resultReference.get(); } public static GenericFutureListener> getWrapper( GenericFutureListener> delegate) { - GenericFutureListener> wrapper = wrappers.get(delegate); + WeakReference>> wrapperReference = wrappers.get(delegate); + if(wrapperReference == null){ + return delegate; + } + GenericFutureListener> wrapper = wrapperReference.get(); return wrapper == null ? delegate : wrapper; } From 830e1d2d4016803e3f1463ae28ab5bd4fd0a5ff8 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 20 Nov 2021 18:34:00 -0800 Subject: [PATCH 05/30] Spotless --- .../api/annotation/support/MethodCache.java | 2 +- .../annotation/support/MethodCacheTest.java | 2 +- .../instrumentation/api/cache/Cache.java | 7 +- .../api/db/SqlStatementSanitizer.java | 4 +- .../instrumentation/api/cache/CacheTest.java | 161 +++++++++--------- .../api/cache/PatchCaffeineTest.java | 44 ++--- .../netty/common/FutureListenerWrappers.java | 36 ++-- .../javaagent/bootstrap/HelperResources.java | 5 +- .../ignore/IgnoredClassLoadersMatcher.java | 3 +- .../javaagent/tooling/HelperInjector.java | 9 +- .../muzzle/AgentCachingPoolStrategy.java | 12 +- settings.gradle.kts | 6 +- 12 files changed, 145 insertions(+), 146 deletions(-) diff --git a/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCache.java b/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCache.java index 3b5398ddaabc..f9bda1ba77da 100644 --- a/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCache.java +++ b/instrumentation-api-annotation-support/src/main/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCache.java @@ -5,11 +5,11 @@ package io.opentelemetry.instrumentation.api.annotation.support; +import io.opentelemetry.instrumentation.api.cache.Cache; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import io.opentelemetry.instrumentation.api.cache.Cache; /** * Implementation of {@link Cache} that uses {@link ClassValue} to store values keyed by {@link diff --git a/instrumentation-api-annotation-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCacheTest.java b/instrumentation-api-annotation-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCacheTest.java index 42aed3fd3636..3d9d9998a5c6 100644 --- a/instrumentation-api-annotation-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCacheTest.java +++ b/instrumentation-api-annotation-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodCacheTest.java @@ -11,13 +11,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import io.opentelemetry.instrumentation.api.cache.Cache; import java.lang.reflect.Method; import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import io.opentelemetry.instrumentation.api.cache.Cache; @ExtendWith(MockitoExtension.class) public class MethodCacheTest { diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java index e1a129b70077..35d68221e805 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java @@ -8,10 +8,11 @@ import java.util.function.Function; import javax.annotation.Nullable; -/** A cache from keys to values. +/** + * A cache from keys to values. * - * Keys are always referenced weakly and are compared using identity comparison, not - * {@link Object#equals(Object)}. + *

Keys are always referenced weakly and are compared using identity comparison, not {@link + * Object#equals(Object)}. */ public interface Cache { diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java index 8244779ed5d1..454b0720ac4c 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java @@ -8,9 +8,9 @@ import static io.opentelemetry.instrumentation.api.db.StatementSanitizationConfig.isStatementSanitizationEnabled; import static io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics.CounterNames.SQL_STATEMENT_SANITIZER_CACHE_MISS; -import java.util.concurrent.ConcurrentHashMap; import com.google.auto.value.AutoValue; import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; /** @@ -20,7 +20,7 @@ public final class SqlStatementSanitizer { private static final SupportabilityMetrics supportability = SupportabilityMetrics.instance(); - //TODO use CHLM + // TODO use CHLM private static final ConcurrentHashMap sqlToStatementInfoCache = new ConcurrentHashMap<>(); diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java index 69c45c85e8a4..5b6902fe552b 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java @@ -13,52 +13,52 @@ class CacheTest { -// @Nested -// @SuppressWarnings("ClassCanBeStatic") -// class StrongKeys { -// @Test -// void unbounded() { -// Cache cache = Cache.builder().build(); -// -// assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); -// cache.remove("bear"); -// -// CaffeineCache caffeineCache = ((CaffeineCache) cache); -// assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); -// assertThat(caffeineCache.keySet()).hasSize(1); -// -// assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); -// assertThat(caffeineCache.keySet()).hasSize(1); -// -// cache.put("dog", "bark"); -// assertThat(cache.get("dog")).isEqualTo("bark"); -// assertThat(cache.get("cat")).isEqualTo("meow"); -// assertThat(cache.get("bear")).isNull(); -// assertThat(caffeineCache.keySet()).hasSize(2); -// assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); -// } -// -// @Test -// void bounded() { -// Cache cache = Cache.builder().setMaximumSize(1).build(); -// -// assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); -// cache.remove("bear"); -// -// CaffeineCache caffeineCache = ((CaffeineCache) cache); -// assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); -// assertThat(caffeineCache.keySet()).hasSize(1); -// -// assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); -// assertThat(caffeineCache.keySet()).hasSize(1); -// -// cache.put("dog", "bark"); -// assertThat(cache.get("dog")).isEqualTo("bark"); -// caffeineCache.cleanup(); -// assertThat(caffeineCache.keySet()).hasSize(1); -// assertThat(cache.computeIfAbsent("cat", unused -> "purr")).isEqualTo("purr"); -// } -// } + // @Nested + // @SuppressWarnings("ClassCanBeStatic") + // class StrongKeys { + // @Test + // void unbounded() { + // Cache cache = Cache.builder().build(); + // + // assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); + // cache.remove("bear"); + // + // CaffeineCache caffeineCache = ((CaffeineCache) cache); + // assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); + // assertThat(caffeineCache.keySet()).hasSize(1); + // + // assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); + // assertThat(caffeineCache.keySet()).hasSize(1); + // + // cache.put("dog", "bark"); + // assertThat(cache.get("dog")).isEqualTo("bark"); + // assertThat(cache.get("cat")).isEqualTo("meow"); + // assertThat(cache.get("bear")).isNull(); + // assertThat(caffeineCache.keySet()).hasSize(2); + // assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); + // } + // + // @Test + // void bounded() { + // Cache cache = Cache.builder().setMaximumSize(1).build(); + // + // assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); + // cache.remove("bear"); + // + // CaffeineCache caffeineCache = ((CaffeineCache) cache); + // assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); + // assertThat(caffeineCache.keySet()).hasSize(1); + // + // assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); + // assertThat(caffeineCache.keySet()).hasSize(1); + // + // cache.put("dog", "bark"); + // assertThat(cache.get("dog")).isEqualTo("bark"); + // caffeineCache.cleanup(); + // assertThat(caffeineCache.keySet()).hasSize(1); + // assertThat(cache.computeIfAbsent("cat", unused -> "purr")).isEqualTo("purr"); + // } + // } @Nested @SuppressWarnings("ClassCanBeStatic") @@ -97,39 +97,40 @@ void unbounded() { await().untilAsserted(() -> assertThat(weakLockFreeCache.size()).isEqualTo(0)); } -// @Test -// void bounded() { -// io.opentelemetry.instrumentation.api.caching.Cache cache = io.opentelemetry.instrumentation.api.caching.Cache.builder().setWeakKeys().setMaximumSize(1).build(); -// -// assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); -// cache.remove("bear"); -// -// CaffeineCache caffeineCache = ((CaffeineCache) cache); -// -// String cat = new String("cat"); -// String dog = new String("dog"); -// assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); -// assertThat(cache.get(cat)).isEqualTo("meow"); -// assertThat(cache.get(new String("cat"))).isNull(); -// assertThat(caffeineCache.keySet()).hasSize(1); -// -// assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow"); -// assertThat(caffeineCache.keySet()).hasSize(1); -// -// cache.put(dog, "bark"); -// assertThat(cache.get(dog)).isEqualTo("bark"); -// assertThat(cache.get(new String("dog"))).isNull(); -// caffeineCache.cleanup(); -// assertThat(caffeineCache.keySet()).hasSize(1); -// dog = null; -// System.gc(); -// // Wait for GC to be reflected. -// await() -// .untilAsserted( -// () -> { -// caffeineCache.cleanup(); -// assertThat(caffeineCache.keySet()).isEmpty(); -// }); -// } + // @Test + // void bounded() { + // io.opentelemetry.instrumentation.api.caching.Cache cache = + // io.opentelemetry.instrumentation.api.caching.Cache.builder().setWeakKeys().setMaximumSize(1).build(); + // + // assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); + // cache.remove("bear"); + // + // CaffeineCache caffeineCache = ((CaffeineCache) cache); + // + // String cat = new String("cat"); + // String dog = new String("dog"); + // assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); + // assertThat(cache.get(cat)).isEqualTo("meow"); + // assertThat(cache.get(new String("cat"))).isNull(); + // assertThat(caffeineCache.keySet()).hasSize(1); + // + // assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow"); + // assertThat(caffeineCache.keySet()).hasSize(1); + // + // cache.put(dog, "bark"); + // assertThat(cache.get(dog)).isEqualTo("bark"); + // assertThat(cache.get(new String("dog"))).isNull(); + // caffeineCache.cleanup(); + // assertThat(caffeineCache.keySet()).hasSize(1); + // dog = null; + // System.gc(); + // // Wait for GC to be reflected. + // await() + // .untilAsserted( + // () -> { + // caffeineCache.cleanup(); + // assertThat(caffeineCache.keySet()).isEmpty(); + // }); + // } } } diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java index e1520295f784..c816153fd665 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java @@ -7,26 +7,26 @@ class PatchCaffeineTest { -// @Test -// void cleanupNotForkJoinTask() { -// AtomicReference errorRef = new AtomicReference<>(); -// io.opentelemetry.instrumentation.api.caching.Cache cache = -// io.opentelemetry.instrumentation.api.caching.Cache.builder() -// .setExecutor( -// task -> { -// try { -// assertThat(task).isNotInstanceOf(ForkJoinTask.class); -// } catch (AssertionError e) { -// errorRef.set(e); -// } -// }) -// .setMaximumSize(1) -// .build(); -// assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); -// assertThat(cache.computeIfAbsent("dog", unused -> "bark")).isEqualTo("bark"); -// AssertionError error = errorRef.get(); -// if (error != null) { -// throw error; -// } -// } + // @Test + // void cleanupNotForkJoinTask() { + // AtomicReference errorRef = new AtomicReference<>(); + // io.opentelemetry.instrumentation.api.caching.Cache cache = + // io.opentelemetry.instrumentation.api.caching.Cache.builder() + // .setExecutor( + // task -> { + // try { + // assertThat(task).isNotInstanceOf(ForkJoinTask.class); + // } catch (AssertionError e) { + // errorRef.set(e); + // } + // }) + // .setMaximumSize(1) + // .build(); + // assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); + // assertThat(cache.computeIfAbsent("dog", unused -> "bark")).isEqualTo("bark"); + // AssertionError error = errorRef.get(); + // if (error != null) { + // throw error; + // } + // } } diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java index bacd3c783a90..956517755883 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java @@ -24,7 +24,8 @@ public final class FutureListenerWrappers { // used to remove the wrapped listener from the netty future, and if the value is collected prior // to the key, that means it's no longer used (referenced) by the netty future anyways. private static final Cache< - GenericFutureListener>, WeakReference>>> + GenericFutureListener>, + WeakReference>>> wrappers = Cache.builder().build(); private static final ClassValue shouldWrap = @@ -45,26 +46,29 @@ public static boolean shouldWrap(GenericFutureListener> list @SuppressWarnings("unchecked") public static GenericFutureListener wrap( Context context, GenericFutureListener> delegate) { - WeakReference>> resultReference = wrappers.computeIfAbsent( - delegate, - key -> { - GenericFutureListener> wrapper; - if (delegate instanceof GenericProgressiveFutureListener) { - wrapper = new WrappedProgressiveFutureListener( - context, (GenericProgressiveFutureListener>) delegate); - } else { - wrapper = new WrappedFutureListener(context, - (GenericFutureListener>) delegate); - } - return new WeakReference<>(wrapper); - }); + WeakReference>> resultReference = + wrappers.computeIfAbsent( + delegate, + key -> { + GenericFutureListener> wrapper; + if (delegate instanceof GenericProgressiveFutureListener) { + wrapper = + new WrappedProgressiveFutureListener( + context, (GenericProgressiveFutureListener>) delegate); + } else { + wrapper = + new WrappedFutureListener(context, (GenericFutureListener>) delegate); + } + return new WeakReference<>(wrapper); + }); return resultReference.get(); } public static GenericFutureListener> getWrapper( GenericFutureListener> delegate) { - WeakReference>> wrapperReference = wrappers.get(delegate); - if(wrapperReference == null){ + WeakReference>> wrapperReference = + wrappers.get(delegate); + if (wrapperReference == null) { return delegate; } GenericFutureListener> wrapper = wrapperReference.get(); diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java index 80ab4173865b..d4c901b33d69 100644 --- a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java @@ -5,10 +5,10 @@ package io.opentelemetry.javaagent.bootstrap; +import io.opentelemetry.instrumentation.api.cache.Cache; import java.net.URL; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import io.opentelemetry.instrumentation.api.cache.Cache; /** * A holder of resources needed by instrumentation. We store them in the bootstrap classloader so @@ -17,8 +17,7 @@ */ public final class HelperResources { - private static final Cache> RESOURCES = - Cache.builder().build(); + private static final Cache> RESOURCES = Cache.builder().build(); /** Registers the {@code payload} to be available to instrumentation at {@code path}. */ public static void register(ClassLoader classLoader, String path, URL url) { diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java index e10da74c2afe..6d170f559ad3 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java @@ -17,8 +17,7 @@ public class IgnoredClassLoadersMatcher extends ElementMatcher.Junction.Abstract private static final Logger logger = LoggerFactory.getLogger(IgnoredClassLoadersMatcher.class); /* Cache of classloader-instance -> (true|false). True = skip instrumentation. False = safe to instrument. */ - private static final Cache skipCache = - Cache.builder().build(); + private static final Cache skipCache = Cache.builder().build(); private final Trie ignoredClassLoaders; diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java index 00b446a34885..842694a94c72 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java @@ -58,8 +58,7 @@ public String toString() { } }; - private static final Cache, Boolean> injectedClasses = - Cache.builder().build(); + private static final Cache, Boolean> injectedClasses = Cache.builder().build(); private final String requestingName; @@ -69,10 +68,8 @@ public String toString() { @Nullable private final Instrumentation instrumentation; private final Map dynamicTypeMap = new LinkedHashMap<>(); - private final Cache injectedClassLoaders = - Cache.builder().build(); - private final Cache resourcesInjectedClassLoaders = - Cache.builder().build(); + private final Cache injectedClassLoaders = Cache.builder().build(); + private final Cache resourcesInjectedClassLoaders = Cache.builder().build(); private final List> helperModules = new CopyOnWriteArrayList<>(); diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java index 647fd70f8d7d..ccb0c99ca9d2 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java @@ -5,8 +5,8 @@ package io.opentelemetry.javaagent.tooling.muzzle; -import java.lang.ref.WeakReference; import io.opentelemetry.instrumentation.api.cache.Cache; +import java.lang.ref.WeakReference; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.description.annotation.AnnotationList; import net.bytebuddy.description.method.MethodDescription; @@ -48,15 +48,13 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy { *

  • Allow for quick fast path equivalence check of composite keys * */ - final Cache> loaderRefCache = - Cache.builder().build(); + final Cache> loaderRefCache = Cache.builder().build(); /** * Single shared Type.Resolution cache -- uses a composite key -- conceptually of loader & name */ - //TODO .setMaximumSize(TYPE_CAPACITY) - final Cache sharedResolutionCache = - Cache.builder().build(); + // TODO .setMaximumSize(TYPE_CAPACITY) + final Cache sharedResolutionCache = Cache.builder().build(); // fast path for bootstrap final SharedResolutionCacheAdapter bootstrapCacheProvider = @@ -211,7 +209,7 @@ static final class SharedResolutionCacheAdapter implements TypePool.CacheProvide @Override public TypePool.Resolution find(String className) { - //TODO this will not work with WeakLockFreeCache, as it uses instance comparison, not equals + // TODO this will not work with WeakLockFreeCache, as it uses instance comparison, not equals TypePool.Resolution existingResolution = sharedResolutionCache.get(new TypeCacheKey(loaderHash, loaderRef, className)); if (existingResolution != null) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 4cd6970d737a..9c85298cad93 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -67,9 +67,9 @@ include(":javaagent") include(":bom-alpha") include(":instrumentation-api") -//include(":instrumentation-api-caching") -//include(":instrumentation-api-caching:caffeine2") -//include(":instrumentation-api-caching:caffeine3") +// include(":instrumentation-api-caching") +// include(":instrumentation-api-caching:caffeine2") +// include(":instrumentation-api-caching:caffeine3") include(":javaagent-instrumentation-api") include(":instrumentation-api-annotation-support") From 4abddb304387f5735cb1c95e457ee03095c12abe Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 20 Nov 2021 19:18:22 -0800 Subject: [PATCH 06/30] Update ClassNames and SpanNames --- .../opentelemetry/instrumentation/api/tracer/ClassNames.java | 4 ++-- .../opentelemetry/instrumentation/api/tracer/SpanNames.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/ClassNames.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/ClassNames.java index 8e1fac2ada0a..bece2f0975c7 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/ClassNames.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/ClassNames.java @@ -5,11 +5,11 @@ package io.opentelemetry.instrumentation.api.tracer; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; public final class ClassNames { - private static final Cache, String> simpleNames = Cache.builder().setWeakKeys().build(); + private static final Cache, String> simpleNames = Cache.builder().build(); /** * This method is used to generate a simple name based on a given class reference, e.g. for use in diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/SpanNames.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/SpanNames.java index 8b3999124fa2..c42c205f0cdd 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/SpanNames.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/SpanNames.java @@ -5,7 +5,7 @@ package io.opentelemetry.instrumentation.api.tracer; -import io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.instrumentation.api.util.ClassAndMethod; import java.lang.reflect.Method; import java.util.Map; @@ -15,7 +15,7 @@ public final class SpanNames { private static final Cache, Map> spanNameCaches = - Cache.builder().setWeakKeys().build(); + Cache.builder().build(); /** * This method is used to generate a span name based on a method. Anonymous classes are named From 7bdf3e04406ff1bb73e8031d46b2adfac4e2c51a Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 20 Nov 2021 19:23:55 -0800 Subject: [PATCH 07/30] Compilation and comment --- .../io/opentelemetry/instrumentation/api/cache/Cache.java | 1 + .../api/internal/RuntimeVirtualFieldSupplier.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java index 35d68221e805..1a65e8f585fb 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java @@ -14,6 +14,7 @@ *

    Keys are always referenced weakly and are compared using identity comparison, not {@link * Object#equals(Object)}. */ +// TODO (trask) rename since now just a weak-keyed map public interface Cache { /** Returns a new {@link CacheBuilder} to configure a {@link Cache}. */ diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java index d324794f4b2e..fdb78be65119 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java @@ -40,13 +40,13 @@ public static VirtualFieldSupplier get() { private static final class CacheBasedVirtualFieldSupplier implements VirtualFieldSupplier { private final Cache, Cache, VirtualField>> - ownerToFieldToImplementationMap = Cache.builder().setWeakKeys().build(); + ownerToFieldToImplementationMap = Cache.builder().build(); @Override public VirtualField find(Class type, Class fieldType) { return (VirtualField) ownerToFieldToImplementationMap - .computeIfAbsent(type, c -> Cache.builder().setWeakKeys().build()) + .computeIfAbsent(type, c -> Cache.builder().build()) .computeIfAbsent(fieldType, c -> new CacheBasedVirtualField<>()); } } From cde78fbbcceba3a4fc643e45d2aaf148583061ca Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Sun, 21 Nov 2021 12:06:47 +0200 Subject: [PATCH 08/30] Add bounded cache and clean interface --- instrumentation-api/build.gradle.kts | 1 + .../api/cache/BoundedCache.java | 40 +++++++++++++++++++ .../instrumentation/api/cache/Cache.java | 24 +++++++++-- .../api/cache/CacheBuilder.java | 16 -------- .../api/db/SqlStatementSanitizer.java | 7 ++-- .../internal/RuntimeVirtualFieldSupplier.java | 6 +-- .../api/tracer/ClassNames.java | 2 +- .../instrumentation/api/tracer/SpanNames.java | 3 +- .../instrumentation/api/cache/CacheTest.java | 4 +- .../netty/common/FutureListenerWrappers.java | 2 +- .../javaagent/bootstrap/HelperResources.java | 2 +- .../ClassLoaderHasClassesNamedMatcher.java | 2 +- .../VirtualFieldImplementationsGenerator.java | 2 +- .../ignore/IgnoredClassLoadersMatcher.java | 2 +- .../InstrumentationModuleInstaller.java | 2 +- .../javaagent/tooling/HelperInjector.java | 6 +-- .../muzzle/AgentCachingPoolStrategy.java | 7 ++-- 17 files changed, 83 insertions(+), 45 deletions(-) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java delete mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/CacheBuilder.java diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts index 77d01ca5139a..c92b835fd6af 100644 --- a/instrumentation-api/build.gradle.kts +++ b/instrumentation-api/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation("io.opentelemetry:opentelemetry-api-metrics") implementation("org.slf4j:slf4j-api") implementation("com.blogspot.mydailyjava:weak-lock-free") + implementation("com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java new file mode 100644 index 000000000000..d65b92f41cf8 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache; + +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import javax.annotation.Nullable; + +final class BoundedCache implements Cache { + + private final ConcurrentMap delegate; + + BoundedCache(ConcurrentMap delegate) { + this.delegate = delegate; + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return delegate.computeIfAbsent(key, mappingFunction); + } + + @Nullable + @Override + public V get(K key) { + return delegate.get(key); + } + + @Override + public void put(K key, V value) { + delegate.put(key, value); + } + + @Override + public void remove(K key) { + delegate.remove(key); + } +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java index 1a65e8f585fb..574db4f36a0d 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.api.cache; +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import java.util.function.Function; import javax.annotation.Nullable; @@ -14,12 +15,27 @@ *

    Keys are always referenced weakly and are compared using identity comparison, not {@link * Object#equals(Object)}. */ -// TODO (trask) rename since now just a weak-keyed map public interface Cache { - /** Returns a new {@link CacheBuilder} to configure a {@link Cache}. */ - static CacheBuilder builder() { - return new CacheBuilder(); + /** + * Returns new unbounded cache + * + *

    Keys are referenced weakly and compared using identity comparison, not {@link + * Object#equals(Object)}. + */ + static Cache weak() { + return new WeakLockFreeCache<>(); + } + + /** + * Returns new bounded cache. + * + *

    Both keys and values are strongly referenced. + */ + static Cache bounded(int capacity) { + ConcurrentLinkedHashMap map = + new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity(capacity).build(); + return new BoundedCache<>(map); } /** diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/CacheBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/CacheBuilder.java deleted file mode 100644 index 4efbeb604754..000000000000 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/CacheBuilder.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.cache; - -/** A builder of {@link Cache}. */ -public final class CacheBuilder { - /** Returns a new {@link Cache} with the settings of this {@link CacheBuilder}. */ - public Cache build() { - return new WeakLockFreeCache<>(); - } - - CacheBuilder() {} -} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java index 454b0720ac4c..8b0a8463981d 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizer.java @@ -9,8 +9,8 @@ import static io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics.CounterNames.SQL_STATEMENT_SANITIZER_CACHE_MISS; import com.google.auto.value.AutoValue; +import io.opentelemetry.instrumentation.api.cache.Cache; import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; -import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; /** @@ -20,9 +20,8 @@ public final class SqlStatementSanitizer { private static final SupportabilityMetrics supportability = SupportabilityMetrics.instance(); - // TODO use CHLM - private static final ConcurrentHashMap sqlToStatementInfoCache = - new ConcurrentHashMap<>(); + private static final Cache sqlToStatementInfoCache = + Cache.bounded(1000); public static SqlStatementInfo sanitize(@Nullable String statement) { return sanitize(statement, SqlDialect.DEFAULT); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java index fdb78be65119..4792ee069fa9 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/RuntimeVirtualFieldSupplier.java @@ -40,19 +40,19 @@ public static VirtualFieldSupplier get() { private static final class CacheBasedVirtualFieldSupplier implements VirtualFieldSupplier { private final Cache, Cache, VirtualField>> - ownerToFieldToImplementationMap = Cache.builder().build(); + ownerToFieldToImplementationMap = Cache.weak(); @Override public VirtualField find(Class type, Class fieldType) { return (VirtualField) ownerToFieldToImplementationMap - .computeIfAbsent(type, c -> Cache.builder().build()) + .computeIfAbsent(type, c -> Cache.weak()) .computeIfAbsent(fieldType, c -> new CacheBasedVirtualField<>()); } } private static final class CacheBasedVirtualField extends VirtualField { - private final Cache cache = Cache.builder().build(); + private final Cache cache = Cache.weak(); @Override @Nullable diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/ClassNames.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/ClassNames.java index bece2f0975c7..6baa06f04af2 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/ClassNames.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/ClassNames.java @@ -9,7 +9,7 @@ public final class ClassNames { - private static final Cache, String> simpleNames = Cache.builder().build(); + private static final Cache, String> simpleNames = Cache.weak(); /** * This method is used to generate a simple name based on a given class reference, e.g. for use in diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/SpanNames.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/SpanNames.java index c42c205f0cdd..dae6f14b235d 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/SpanNames.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/SpanNames.java @@ -14,8 +14,7 @@ public final class SpanNames { - private static final Cache, Map> spanNameCaches = - Cache.builder().build(); + private static final Cache, Map> spanNameCaches = Cache.weak(); /** * This method is used to generate a span name based on a method. Anonymous classes are named diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java index 5b6902fe552b..c5d94a3e898e 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java @@ -18,7 +18,7 @@ class CacheTest { // class StrongKeys { // @Test // void unbounded() { - // Cache cache = Cache.builder().build(); + // Cache cache = Cache.weak(); // // assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); // cache.remove("bear"); @@ -65,7 +65,7 @@ class CacheTest { class WeakKeys { @Test void unbounded() { - Cache cache = Cache.builder().build(); + Cache cache = Cache.weak(); assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); cache.remove("bear"); diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java index 956517755883..f1e3881505e0 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java @@ -26,7 +26,7 @@ public final class FutureListenerWrappers { private static final Cache< GenericFutureListener>, WeakReference>>> - wrappers = Cache.builder().build(); + wrappers = Cache.weak(); private static final ClassValue shouldWrap = new ClassValue() { diff --git a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java index d4c901b33d69..e7bdba3631be 100644 --- a/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java +++ b/javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/HelperResources.java @@ -17,7 +17,7 @@ */ public final class HelperResources { - private static final Cache> RESOURCES = Cache.builder().build(); + private static final Cache> RESOURCES = Cache.weak(); /** Registers the {@code payload} to be available to instrumentation at {@code path}. */ public static void register(ClassLoader classLoader, String path, URL url) { diff --git a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java index 2db90a049ea0..2988bc334fac 100644 --- a/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java +++ b/javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java @@ -12,7 +12,7 @@ class ClassLoaderHasClassesNamedMatcher extends ElementMatcher.Junction.AbstractBase { - private final Cache cache = Cache.builder().build(); + private final Cache cache = Cache.weak(); private final String[] resources; diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/VirtualFieldImplementationsGenerator.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/VirtualFieldImplementationsGenerator.java index 083b6ec8f529..6c5b4af770b7 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/VirtualFieldImplementationsGenerator.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/field/VirtualFieldImplementationsGenerator.java @@ -264,7 +264,7 @@ private MethodVisitor getMethodVisitor(String methodName) { @SuppressWarnings({"UnusedMethod", "UnusedVariable", "MethodCanBeStatic"}) static final class VirtualFieldImplementationTemplate extends VirtualField { private static final VirtualFieldImplementationTemplate INSTANCE = - new VirtualFieldImplementationTemplate(Cache.builder().build()); + new VirtualFieldImplementationTemplate(Cache.weak()); private final Cache map; diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java index 6d170f559ad3..75bb87f56153 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/IgnoredClassLoadersMatcher.java @@ -17,7 +17,7 @@ public class IgnoredClassLoadersMatcher extends ElementMatcher.Junction.Abstract private static final Logger logger = LoggerFactory.getLogger(IgnoredClassLoadersMatcher.class); /* Cache of classloader-instance -> (true|false). True = skip instrumentation. False = safe to instrument. */ - private static final Cache skipCache = Cache.builder().build(); + private static final Cache skipCache = Cache.weak(); private final Trie ignoredClassLoaders; diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java index 2ecd4e33bb37..f7af0c60d6e4 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/InstrumentationModuleInstaller.java @@ -125,7 +125,7 @@ AgentBuilder install( private static class MuzzleMatcher implements AgentBuilder.RawMatcher { private final InstrumentationModule instrumentationModule; private final AtomicBoolean initialized = new AtomicBoolean(false); - private final Cache matchCache = Cache.builder().build(); + private final Cache matchCache = Cache.weak(); private volatile ReferenceMatcher referenceMatcher; private MuzzleMatcher(InstrumentationModule instrumentationModule) { diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java index 842694a94c72..8cffc39d1a3b 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/HelperInjector.java @@ -58,7 +58,7 @@ public String toString() { } }; - private static final Cache, Boolean> injectedClasses = Cache.builder().build(); + private static final Cache, Boolean> injectedClasses = Cache.weak(); private final String requestingName; @@ -68,8 +68,8 @@ public String toString() { @Nullable private final Instrumentation instrumentation; private final Map dynamicTypeMap = new LinkedHashMap<>(); - private final Cache injectedClassLoaders = Cache.builder().build(); - private final Cache resourcesInjectedClassLoaders = Cache.builder().build(); + private final Cache injectedClassLoaders = Cache.weak(); + private final Cache resourcesInjectedClassLoaders = Cache.weak(); private final List> helperModules = new CopyOnWriteArrayList<>(); diff --git a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java index ccb0c99ca9d2..6022940afd56 100644 --- a/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java +++ b/muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/AgentCachingPoolStrategy.java @@ -48,13 +48,13 @@ public class AgentCachingPoolStrategy implements AgentBuilder.PoolStrategy { *

  • Allow for quick fast path equivalence check of composite keys * */ - final Cache> loaderRefCache = Cache.builder().build(); + final Cache> loaderRefCache = Cache.weak(); /** * Single shared Type.Resolution cache -- uses a composite key -- conceptually of loader & name */ - // TODO .setMaximumSize(TYPE_CAPACITY) - final Cache sharedResolutionCache = Cache.builder().build(); + final Cache sharedResolutionCache = + Cache.bounded(TYPE_CAPACITY); // fast path for bootstrap final SharedResolutionCacheAdapter bootstrapCacheProvider = @@ -209,7 +209,6 @@ static final class SharedResolutionCacheAdapter implements TypePool.CacheProvide @Override public TypePool.Resolution find(String className) { - // TODO this will not work with WeakLockFreeCache, as it uses instance comparison, not equals TypePool.Resolution existingResolution = sharedResolutionCache.get(new TypeCacheKey(loaderHash, loaderRef, className)); if (existingResolution != null) { From 437da21b620063b23ac02e56bc33fffd526a0d49 Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Sun, 21 Nov 2021 12:13:59 +0200 Subject: [PATCH 09/30] Polish --- .../api/cache/BoundedCache.java | 5 + .../instrumentation/api/cache/CacheTest.java | 106 ++++-------------- .../api/cache/PatchCaffeineTest.java | 32 ------ settings.gradle.kts | 3 - 4 files changed, 29 insertions(+), 117 deletions(-) delete mode 100644 instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java index d65b92f41cf8..2cddd962efef 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java @@ -37,4 +37,9 @@ public void put(K key, V value) { public void remove(K key) { delegate.remove(key); } + + // Visible for tests + int size() { + return delegate.size(); + } } diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java index c5d94a3e898e..8e10714632be 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java @@ -13,56 +13,34 @@ class CacheTest { - // @Nested - // @SuppressWarnings("ClassCanBeStatic") - // class StrongKeys { - // @Test - // void unbounded() { - // Cache cache = Cache.weak(); - // - // assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); - // cache.remove("bear"); - // - // CaffeineCache caffeineCache = ((CaffeineCache) cache); - // assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - // assertThat(caffeineCache.keySet()).hasSize(1); - // - // assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); - // assertThat(caffeineCache.keySet()).hasSize(1); - // - // cache.put("dog", "bark"); - // assertThat(cache.get("dog")).isEqualTo("bark"); - // assertThat(cache.get("cat")).isEqualTo("meow"); - // assertThat(cache.get("bear")).isNull(); - // assertThat(caffeineCache.keySet()).hasSize(2); - // assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - // } - // - // @Test - // void bounded() { - // Cache cache = Cache.builder().setMaximumSize(1).build(); - // - // assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); - // cache.remove("bear"); - // - // CaffeineCache caffeineCache = ((CaffeineCache) cache); - // assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - // assertThat(caffeineCache.keySet()).hasSize(1); - // - // assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); - // assertThat(caffeineCache.keySet()).hasSize(1); - // - // cache.put("dog", "bark"); - // assertThat(cache.get("dog")).isEqualTo("bark"); - // caffeineCache.cleanup(); - // assertThat(caffeineCache.keySet()).hasSize(1); - // assertThat(cache.computeIfAbsent("cat", unused -> "purr")).isEqualTo("purr"); - // } - // } + @Nested + @SuppressWarnings("ClassCanBeStatic") + class StrongKeys { + @Test + void bounded() { + Cache cache = Cache.bounded(1); + + assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); + cache.remove("bear"); + + BoundedCache boundedCache = ((BoundedCache) cache); + assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); + assertThat(boundedCache.size()).isEqualTo(1); + + assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); + assertThat(boundedCache.size()).isEqualTo(1); + + cache.put("dog", "bark"); + assertThat(cache.get("dog")).isEqualTo("bark"); + assertThat(boundedCache.size()).isEqualTo(1); + assertThat(cache.computeIfAbsent("cat", unused -> "purr")).isEqualTo("purr"); + } + } @Nested @SuppressWarnings("ClassCanBeStatic") class WeakKeys { + @SuppressWarnings("StringOperationCanBeSimplified") @Test void unbounded() { Cache cache = Cache.weak(); @@ -96,41 +74,5 @@ void unbounded() { // Wait for GC to be reflected. await().untilAsserted(() -> assertThat(weakLockFreeCache.size()).isEqualTo(0)); } - - // @Test - // void bounded() { - // io.opentelemetry.instrumentation.api.caching.Cache cache = - // io.opentelemetry.instrumentation.api.caching.Cache.builder().setWeakKeys().setMaximumSize(1).build(); - // - // assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); - // cache.remove("bear"); - // - // CaffeineCache caffeineCache = ((CaffeineCache) cache); - // - // String cat = new String("cat"); - // String dog = new String("dog"); - // assertThat(cache.computeIfAbsent(cat, unused -> "meow")).isEqualTo("meow"); - // assertThat(cache.get(cat)).isEqualTo("meow"); - // assertThat(cache.get(new String("cat"))).isNull(); - // assertThat(caffeineCache.keySet()).hasSize(1); - // - // assertThat(cache.computeIfAbsent(cat, unused -> "bark")).isEqualTo("meow"); - // assertThat(caffeineCache.keySet()).hasSize(1); - // - // cache.put(dog, "bark"); - // assertThat(cache.get(dog)).isEqualTo("bark"); - // assertThat(cache.get(new String("dog"))).isNull(); - // caffeineCache.cleanup(); - // assertThat(caffeineCache.keySet()).hasSize(1); - // dog = null; - // System.gc(); - // // Wait for GC to be reflected. - // await() - // .untilAsserted( - // () -> { - // caffeineCache.cleanup(); - // assertThat(caffeineCache.keySet()).isEmpty(); - // }); - // } } } diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java deleted file mode 100644 index c816153fd665..000000000000 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/PatchCaffeineTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.cache; - -class PatchCaffeineTest { - - // @Test - // void cleanupNotForkJoinTask() { - // AtomicReference errorRef = new AtomicReference<>(); - // io.opentelemetry.instrumentation.api.caching.Cache cache = - // io.opentelemetry.instrumentation.api.caching.Cache.builder() - // .setExecutor( - // task -> { - // try { - // assertThat(task).isNotInstanceOf(ForkJoinTask.class); - // } catch (AssertionError e) { - // errorRef.set(e); - // } - // }) - // .setMaximumSize(1) - // .build(); - // assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - // assertThat(cache.computeIfAbsent("dog", unused -> "bark")).isEqualTo("bark"); - // AssertionError error = errorRef.get(); - // if (error != null) { - // throw error; - // } - // } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9c85298cad93..b7c9b6fd12df 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -67,9 +67,6 @@ include(":javaagent") include(":bom-alpha") include(":instrumentation-api") -// include(":instrumentation-api-caching") -// include(":instrumentation-api-caching:caffeine2") -// include(":instrumentation-api-caching:caffeine3") include(":javaagent-instrumentation-api") include(":instrumentation-api-annotation-support") From fdc64bb46f15fa16c70bc8f86b6e2b065b7d6a6c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sun, 21 Nov 2021 13:20:37 -0800 Subject: [PATCH 10/30] Add comment --- .../api/instrumenter/http/HttpHeaderAttributes.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java index 444fa80f28b8..f426b680d9f6 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpHeaderAttributes.java @@ -8,12 +8,16 @@ import io.opentelemetry.api.common.AttributeKey; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; final class HttpHeaderAttributes { - private static final ConcurrentHashMap>> requestKeysCache = + // these are naturally bounded because they only store keys listed in + // otel.instrumentation.http.capture-headers.server.request and + // otel.instrumentation.http.capture-headers.server.response + private static final ConcurrentMap>> requestKeysCache = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap>> responseKeysCache = + private static final ConcurrentMap>> responseKeysCache = new ConcurrentHashMap<>(); static AttributeKey> requestAttributeKey(String headerName) { From f7385d450bbb2917efb2992c442104ef3afc0b6d Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Mon, 22 Nov 2021 09:39:16 +0200 Subject: [PATCH 11/30] Vendor ConcurrentLinkedHashMap in --- instrumentation-api/build.gradle.kts | 1 - .../instrumentation/api/cache/Cache.java | 2 +- .../ConcurrentLinkedHashMap.java | 1578 +++++++++++++++++ .../concurrentlinkedhashmap/EntryWeigher.java | 30 + .../EvictionListener.java | 36 + .../api/cache/concurrentlinkedhashmap/LICENSE | 201 +++ .../concurrentlinkedhashmap/LinkedDeque.java | 432 +++++ .../api/cache/concurrentlinkedhashmap/NOTICE | 7 + .../concurrentlinkedhashmap/Weigher.java | 29 + .../concurrentlinkedhashmap/Weighers.java | 263 +++ .../concurrentlinkedhashmap/package-info.java | 41 + 11 files changed, 2618 insertions(+), 2 deletions(-) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EntryWeigher.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EvictionListener.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LICENSE create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LinkedDeque.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/NOTICE create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weigher.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weighers.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/package-info.java diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts index c92b835fd6af..77d01ca5139a 100644 --- a/instrumentation-api/build.gradle.kts +++ b/instrumentation-api/build.gradle.kts @@ -26,7 +26,6 @@ dependencies { implementation("io.opentelemetry:opentelemetry-api-metrics") implementation("org.slf4j:slf4j-api") implementation("com.blogspot.mydailyjava:weak-lock-free") - implementation("com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java index 574db4f36a0d..32901b1e1548 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java @@ -5,7 +5,7 @@ package io.opentelemetry.instrumentation.api.cache; -import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import java.util.function.Function; import javax.annotation.Nullable; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java new file mode 100644 index 000000000000..f8bc7bd5a4fa --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java @@ -0,0 +1,1578 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; + +import static io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap.DrainStatus.IDLE; +import static io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap.DrainStatus.PROCESSING; +import static io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap.DrainStatus.REQUIRED; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableMap; +import static java.util.Collections.unmodifiableSet; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractQueue; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * A hash table supporting full concurrency of retrievals, adjustable expected concurrency for + * updates, and a maximum capacity to bound the map by. This implementation differs from {@link + * ConcurrentHashMap} in that it maintains a page replacement algorithm that is used to evict an + * entry when the map has exceeded its capacity. Unlike the Java Collections Framework, + * this map does not have a publicly visible constructor and instances are created through a {@link + * Builder}. + * + *

    An entry is evicted from the map when the weighted capacity exceeds its maximum + * weighted capacity threshold. A {@link EntryWeigher} determines how many units of capacity + * that an entry consumes. The default weigher assigns each value a weight of 1 to bound + * the map by the total number of key-value pairs. A map that holds collections may choose to weigh + * values by the number of elements in the collection and bound the map by the total number of + * elements that it contains. A change to a value that modifies its weight requires that an update + * operation is performed on the map. + * + *

    An {@link EvictionListener} may be supplied for notification when an entry is evicted from the + * map. This listener is invoked on a caller's thread and will not block other threads from + * operating on the map. An implementation should be aware that the caller's thread will not expect + * long execution times or failures as a side effect of the listener being notified. Execution + * safety and a fast turn around time can be achieved by performing the operation asynchronously, + * such as by submitting a task to an {@link java.util.concurrent.ExecutorService}. + * + *

    The concurrency level determines the number of threads that can concurrently modify + * the table. Using a significantly higher or lower value than needed can waste space or lead to + * thread contention, but an estimate within an order of magnitude of the ideal value does not + * usually have a noticeable impact. Because placement in hash tables is essentially random, the + * actual concurrency will vary. + * + *

    This class and its views and iterators implement all of the optional methods of the + * {@link Map} and {@link Iterator} interfaces. + * + *

    Like {@link java.util.Hashtable} but unlike {@link HashMap}, this class does not + * allow null to be used as a key or value. Unlike {@link java.util.LinkedHashMap}, this + * class does not provide predictable iteration order. A snapshot of the keys and entries + * may be obtained in ascending and descending order of retention. + * + * @author ben.manes@gmail.com (Ben Manes) + * @param the type of keys maintained by this map + * @param the type of mapped values + * @see + * http://code.google.com/p/concurrentlinkedhashmap/ + */ +@ThreadSafe +public final class ConcurrentLinkedHashMap extends AbstractMap + implements ConcurrentMap, Serializable { + + /* + * This class performs a best-effort bounding of a ConcurrentHashMap using a + * page-replacement algorithm to determine which entries to evict when the + * capacity is exceeded. + * + * The page replacement algorithm's data structures are kept eventually + * consistent with the map. An update to the map and recording of reads may + * not be immediately reflected on the algorithm's data structures. These + * structures are guarded by a lock and operations are applied in batches to + * avoid lock contention. The penalty of applying the batches is spread across + * threads so that the amortized cost is slightly higher than performing just + * the ConcurrentHashMap operation. + * + * A memento of the reads and writes that were performed on the map are + * recorded in buffers. These buffers are drained at the first opportunity + * after a write or when the read buffer exceeds a threshold size. The reads + * are recorded in a lossy buffer, allowing the reordering operations to be + * discarded if the draining process cannot keep up. Due to the concurrent + * nature of the read and write operations a strict policy ordering is not + * possible, but is observably strict when single threaded. + * + * Due to a lack of a strict ordering guarantee, a task can be executed + * out-of-order, such as a removal followed by its addition. The state of the + * entry is encoded within the value's weight. + * + * Alive: The entry is in both the hash-table and the page replacement policy. + * This is represented by a positive weight. + * + * Retired: The entry is not in the hash-table and is pending removal from the + * page replacement policy. This is represented by a negative weight. + * + * Dead: The entry is not in the hash-table and is not in the page replacement + * policy. This is represented by a weight of zero. + * + * The Least Recently Used page replacement algorithm was chosen due to its + * simplicity, high hit rate, and ability to be implemented with O(1) time + * complexity. + */ + + /** The number of CPUs */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** The maximum weighted capacity of the map. */ + static final long MAXIMUM_CAPACITY = Long.MAX_VALUE - Integer.MAX_VALUE; + + /** The number of read buffers to use. */ + static final int NUMBER_OF_READ_BUFFERS = ceilingNextPowerOfTwo(NCPU); + + /** Mask value for indexing into the read buffers. */ + static final int READ_BUFFERS_MASK = NUMBER_OF_READ_BUFFERS - 1; + + /** The number of pending read operations before attempting to drain. */ + static final int READ_BUFFER_THRESHOLD = 32; + + /** The maximum number of read operations to perform per amortized drain. */ + static final int READ_BUFFER_DRAIN_THRESHOLD = 2 * READ_BUFFER_THRESHOLD; + + /** The maximum number of pending reads per buffer. */ + static final int READ_BUFFER_SIZE = 2 * READ_BUFFER_DRAIN_THRESHOLD; + + /** Mask value for indexing into the read buffer. */ + static final int READ_BUFFER_INDEX_MASK = READ_BUFFER_SIZE - 1; + + /** The maximum number of write operations to perform per amortized drain. */ + static final int WRITE_BUFFER_DRAIN_THRESHOLD = 16; + + /** A queue that discards all entries. */ + static final Queue DISCARDING_QUEUE = new DiscardingQueue(); + + static int ceilingNextPowerOfTwo(int x) { + // From Hacker's Delight, Chapter 3, Harry S. Warren Jr. + return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1)); + } + + // The backing data store holding the key-value associations + final ConcurrentMap> data; + final int concurrencyLevel; + + // These fields provide support to bound the map by a maximum capacity + @GuardedBy("evictionLock") + final long[] readBufferReadCount; + + @GuardedBy("evictionLock") + final LinkedDeque> evictionDeque; + + @GuardedBy("evictionLock") // must write under lock + final AtomicLong weightedSize; + + @GuardedBy("evictionLock") // must write under lock + final AtomicLong capacity; + + final Lock evictionLock; + final Queue writeBuffer; + final AtomicLong[] readBufferWriteCount; + final AtomicLong[] readBufferDrainAtWriteCount; + final AtomicReference>[][] readBuffers; + + final AtomicReference drainStatus; + final EntryWeigher weigher; + + // These fields provide support for notifying a listener. + final Queue> pendingNotifications; + final EvictionListener listener; + + transient Set keySet; + transient Collection values; + transient Set> entrySet; + + /** Creates an instance based on the builder's configuration. */ + @SuppressWarnings({"unchecked", "cast"}) + private ConcurrentLinkedHashMap(Builder builder) { + // The data store and its maximum capacity + concurrencyLevel = builder.concurrencyLevel; + capacity = new AtomicLong(Math.min(builder.capacity, MAXIMUM_CAPACITY)); + data = new ConcurrentHashMap>(builder.initialCapacity, 0.75f, concurrencyLevel); + + // The eviction support + weigher = builder.weigher; + evictionLock = new ReentrantLock(); + weightedSize = new AtomicLong(); + evictionDeque = new LinkedDeque>(); + writeBuffer = new ConcurrentLinkedQueue(); + drainStatus = new AtomicReference(IDLE); + + readBufferReadCount = new long[NUMBER_OF_READ_BUFFERS]; + readBufferWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS]; + readBufferDrainAtWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS]; + readBuffers = new AtomicReference[NUMBER_OF_READ_BUFFERS][READ_BUFFER_SIZE]; + for (int i = 0; i < NUMBER_OF_READ_BUFFERS; i++) { + readBufferWriteCount[i] = new AtomicLong(); + readBufferDrainAtWriteCount[i] = new AtomicLong(); + readBuffers[i] = new AtomicReference[READ_BUFFER_SIZE]; + for (int j = 0; j < READ_BUFFER_SIZE; j++) { + readBuffers[i][j] = new AtomicReference>(); + } + } + + // The notification queue and listener + listener = builder.listener; + pendingNotifications = + (listener == DiscardingListener.INSTANCE) + ? (Queue>) DISCARDING_QUEUE + : new ConcurrentLinkedQueue>(); + } + + /** Ensures that the object is not null. */ + static void checkNotNull(Object o) { + if (o == null) { + throw new NullPointerException(); + } + } + + /** Ensures that the argument expression is true. */ + static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** Ensures that the state expression is true. */ + static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /* ---------------- Eviction Support -------------- */ + + /** + * Retrieves the maximum weighted capacity of the map. + * + * @return the maximum weighted capacity + */ + public long capacity() { + return capacity.get(); + } + + /** + * Sets the maximum weighted capacity of the map and eagerly evicts entries until it shrinks to + * the appropriate size. + * + * @param capacity the maximum weighted capacity of the map + * @throws IllegalArgumentException if the capacity is negative + */ + public void setCapacity(long capacity) { + checkArgument(capacity >= 0); + evictionLock.lock(); + try { + this.capacity.lazySet(Math.min(capacity, MAXIMUM_CAPACITY)); + drainBuffers(); + evict(); + } finally { + evictionLock.unlock(); + } + notifyListener(); + } + + /** Determines whether the map has exceeded its capacity. */ + @GuardedBy("evictionLock") + boolean hasOverflowed() { + return weightedSize.get() > capacity.get(); + } + + /** + * Evicts entries from the map while it exceeds the capacity and appends evicted entries to the + * notification queue for processing. + */ + @GuardedBy("evictionLock") + void evict() { + // Attempts to evict entries from the map if it exceeds the maximum + // capacity. If the eviction fails due to a concurrent removal of the + // victim, that removal may cancel out the addition that triggered this + // eviction. The victim is eagerly unlinked before the removal task so + // that if an eviction is still required then a new victim will be chosen + // for removal. + while (hasOverflowed()) { + final Node node = evictionDeque.poll(); + + // If weighted values are used, then the pending operations will adjust + // the size to reflect the correct weight + if (node == null) { + return; + } + + // Notify the listener only if the entry was evicted + if (data.remove(node.key, node)) { + pendingNotifications.add(node); + } + + makeDead(node); + } + } + + /** + * Performs the post-processing work required after a read. + * + * @param node the entry in the page replacement policy + */ + void afterRead(Node node) { + final int bufferIndex = readBufferIndex(); + final long writeCount = recordRead(bufferIndex, node); + drainOnReadIfNeeded(bufferIndex, writeCount); + notifyListener(); + } + + /** Returns the index to the read buffer to record into. */ + static int readBufferIndex() { + // A buffer is chosen by the thread's id so that tasks are distributed in a + // pseudo evenly manner. This helps avoid hot entries causing contention + // due to other threads trying to append to the same buffer. + return ((int) Thread.currentThread().getId()) & READ_BUFFERS_MASK; + } + + /** + * Records a read in the buffer and return its write count. + * + * @param bufferIndex the index to the chosen read buffer + * @param node the entry in the page replacement policy + * @return the number of writes on the chosen read buffer + */ + long recordRead(int bufferIndex, Node node) { + // The location in the buffer is chosen in a racy fashion as the increment + // is not atomic with the insertion. This means that concurrent reads can + // overlap and overwrite one another, resulting in a lossy buffer. + final AtomicLong counter = readBufferWriteCount[bufferIndex]; + final long writeCount = counter.get(); + counter.lazySet(writeCount + 1); + + final int index = (int) (writeCount & READ_BUFFER_INDEX_MASK); + readBuffers[bufferIndex][index].lazySet(node); + + return writeCount; + } + + /** + * Attempts to drain the buffers if it is determined to be needed when post-processing a read. + * + * @param bufferIndex the index to the chosen read buffer + * @param writeCount the number of writes on the chosen read buffer + */ + void drainOnReadIfNeeded(int bufferIndex, long writeCount) { + final long pending = (writeCount - readBufferDrainAtWriteCount[bufferIndex].get()); + final boolean delayable = (pending < READ_BUFFER_THRESHOLD); + final DrainStatus status = drainStatus.get(); + if (status.shouldDrainBuffers(delayable)) { + tryToDrainBuffers(); + } + } + + /** + * Performs the post-processing work required after a write. + * + * @param task the pending operation to be applied + */ + void afterWrite(Runnable task) { + writeBuffer.add(task); + drainStatus.lazySet(REQUIRED); + tryToDrainBuffers(); + notifyListener(); + } + + /** + * Attempts to acquire the eviction lock and apply the pending operations, up to the amortized + * threshold, to the page replacement policy. + */ + void tryToDrainBuffers() { + if (evictionLock.tryLock()) { + try { + drainStatus.lazySet(PROCESSING); + drainBuffers(); + } finally { + drainStatus.compareAndSet(PROCESSING, IDLE); + evictionLock.unlock(); + } + } + } + + /** Drains the read and write buffers up to an amortized threshold. */ + @GuardedBy("evictionLock") + void drainBuffers() { + drainReadBuffers(); + drainWriteBuffer(); + } + + /** Drains the read buffers, each up to an amortized threshold. */ + @GuardedBy("evictionLock") + void drainReadBuffers() { + final int start = (int) Thread.currentThread().getId(); + final int end = start + NUMBER_OF_READ_BUFFERS; + for (int i = start; i < end; i++) { + drainReadBuffer(i & READ_BUFFERS_MASK); + } + } + + /** Drains the read buffer up to an amortized threshold. */ + @GuardedBy("evictionLock") + void drainReadBuffer(int bufferIndex) { + final long writeCount = readBufferWriteCount[bufferIndex].get(); + for (int i = 0; i < READ_BUFFER_DRAIN_THRESHOLD; i++) { + final int index = (int) (readBufferReadCount[bufferIndex] & READ_BUFFER_INDEX_MASK); + final AtomicReference> slot = readBuffers[bufferIndex][index]; + final Node node = slot.get(); + if (node == null) { + break; + } + + slot.lazySet(null); + applyRead(node); + readBufferReadCount[bufferIndex]++; + } + readBufferDrainAtWriteCount[bufferIndex].lazySet(writeCount); + } + + /** Updates the node's location in the page replacement policy. */ + @GuardedBy("evictionLock") + void applyRead(Node node) { + // An entry may be scheduled for reordering despite having been removed. + // This can occur when the entry was concurrently read while a writer was + // removing it. If the entry is no longer linked then it does not need to + // be processed. + if (evictionDeque.contains(node)) { + evictionDeque.moveToBack(node); + } + } + + /** Drains the read buffer up to an amortized threshold. */ + @GuardedBy("evictionLock") + void drainWriteBuffer() { + for (int i = 0; i < WRITE_BUFFER_DRAIN_THRESHOLD; i++) { + final Runnable task = writeBuffer.poll(); + if (task == null) { + break; + } + task.run(); + } + } + + /** + * Attempts to transition the node from the alive state to the retired state. + * + * @param node the entry in the page replacement policy + * @param expect the expected weighted value + * @return if successful + */ + boolean tryToRetire(Node node, WeightedValue expect) { + if (expect.isAlive()) { + final WeightedValue retired = new WeightedValue(expect.value, -expect.weight); + return node.compareAndSet(expect, retired); + } + return false; + } + + /** + * Atomically transitions the node from the alive state to the retired state, if + * a valid transition. + * + * @param node the entry in the page replacement policy + */ + void makeRetired(Node node) { + for (; ; ) { + final WeightedValue current = node.get(); + if (!current.isAlive()) { + return; + } + final WeightedValue retired = new WeightedValue(current.value, -current.weight); + if (node.compareAndSet(current, retired)) { + return; + } + } + } + + /** + * Atomically transitions the node to the dead state and decrements the + * weightedSize. + * + * @param node the entry in the page replacement policy + */ + @GuardedBy("evictionLock") + void makeDead(Node node) { + for (; ; ) { + WeightedValue current = node.get(); + WeightedValue dead = new WeightedValue(current.value, 0); + if (node.compareAndSet(current, dead)) { + weightedSize.lazySet(weightedSize.get() - Math.abs(current.weight)); + return; + } + } + } + + /** Notifies the listener of entries that were evicted. */ + void notifyListener() { + Node node; + while ((node = pendingNotifications.poll()) != null) { + listener.onEviction(node.key, node.getValue()); + } + } + + /** Adds the node to the page replacement policy. */ + final class AddTask implements Runnable { + final Node node; + final int weight; + + AddTask(Node node, int weight) { + this.weight = weight; + this.node = node; + } + + @Override + @GuardedBy("evictionLock") + public void run() { + weightedSize.lazySet(weightedSize.get() + weight); + + // ignore out-of-order write operations + if (node.get().isAlive()) { + evictionDeque.add(node); + evict(); + } + } + } + + /** Removes a node from the page replacement policy. */ + final class RemovalTask implements Runnable { + final Node node; + + RemovalTask(Node node) { + this.node = node; + } + + @Override + @GuardedBy("evictionLock") + public void run() { + // add may not have been processed yet + evictionDeque.remove(node); + makeDead(node); + } + } + + /** Updates the weighted size and evicts an entry on overflow. */ + final class UpdateTask implements Runnable { + final int weightDifference; + final Node node; + + public UpdateTask(Node node, int weightDifference) { + this.weightDifference = weightDifference; + this.node = node; + } + + @Override + @GuardedBy("evictionLock") + public void run() { + weightedSize.lazySet(weightedSize.get() + weightDifference); + applyRead(node); + evict(); + } + } + + /* ---------------- Concurrent Map Support -------------- */ + + @Override + public boolean isEmpty() { + return data.isEmpty(); + } + + @Override + public int size() { + return data.size(); + } + + /** + * Returns the weighted size of this map. + * + * @return the combined weight of the values in this map + */ + public long weightedSize() { + return Math.max(0, weightedSize.get()); + } + + @Override + public void clear() { + evictionLock.lock(); + try { + // Discard all entries + Node node; + while ((node = evictionDeque.poll()) != null) { + data.remove(node.key, node); + makeDead(node); + } + + // Discard all pending reads + for (AtomicReference>[] buffer : readBuffers) { + for (AtomicReference> slot : buffer) { + slot.lazySet(null); + } + } + + // Apply all pending writes + Runnable task; + while ((task = writeBuffer.poll()) != null) { + task.run(); + } + } finally { + evictionLock.unlock(); + } + } + + @Override + public boolean containsKey(Object key) { + return data.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + checkNotNull(value); + + for (Node node : data.values()) { + if (node.getValue().equals(value)) { + return true; + } + } + return false; + } + + @Override + public V get(Object key) { + final Node node = data.get(key); + if (node == null) { + return null; + } + afterRead(node); + return node.getValue(); + } + + /** + * Returns the value to which the specified key is mapped, or {@code null} if this map contains no + * mapping for the key. This method differs from {@link #get(Object)} in that it does not record + * the operation with the page replacement policy. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or {@code null} if this map contains no + * mapping for the key + * @throws NullPointerException if the specified key is null + */ + public V getQuietly(Object key) { + final Node node = data.get(key); + return (node == null) ? null : node.getValue(); + } + + @Override + public V put(K key, V value) { + return put(key, value, false); + } + + @Override + public V putIfAbsent(K key, V value) { + return put(key, value, true); + } + + /** + * Adds a node to the list and the data store. If an existing node is found, then its value is + * updated if allowed. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @param onlyIfAbsent a write is performed only if the key is not already associated with a value + * @return the prior value in the data store or null if no mapping was found + */ + V put(K key, V value, boolean onlyIfAbsent) { + checkNotNull(key); + checkNotNull(value); + + final int weight = weigher.weightOf(key, value); + final WeightedValue weightedValue = new WeightedValue(value, weight); + final Node node = new Node(key, weightedValue); + + for (; ; ) { + final Node prior = data.putIfAbsent(node.key, node); + if (prior == null) { + afterWrite(new AddTask(node, weight)); + return null; + } else if (onlyIfAbsent) { + afterRead(prior); + return prior.getValue(); + } + for (; ; ) { + final WeightedValue oldWeightedValue = prior.get(); + if (!oldWeightedValue.isAlive()) { + break; + } + + if (prior.compareAndSet(oldWeightedValue, weightedValue)) { + final int weightedDifference = weight - oldWeightedValue.weight; + if (weightedDifference == 0) { + afterRead(prior); + } else { + afterWrite(new UpdateTask(prior, weightedDifference)); + } + return oldWeightedValue.value; + } + } + } + } + + @Override + public V remove(Object key) { + final Node node = data.remove(key); + if (node == null) { + return null; + } + + makeRetired(node); + afterWrite(new RemovalTask(node)); + return node.getValue(); + } + + @Override + public boolean remove(Object key, Object value) { + final Node node = data.get(key); + if ((node == null) || (value == null)) { + return false; + } + + WeightedValue weightedValue = node.get(); + for (; ; ) { + if (weightedValue.contains(value)) { + if (tryToRetire(node, weightedValue)) { + if (data.remove(key, node)) { + afterWrite(new RemovalTask(node)); + return true; + } + } else { + weightedValue = node.get(); + if (weightedValue.isAlive()) { + // retry as an intermediate update may have replaced the value with + // an equal instance that has a different reference identity + continue; + } + } + } + return false; + } + } + + @Override + public V replace(K key, V value) { + checkNotNull(key); + checkNotNull(value); + + final int weight = weigher.weightOf(key, value); + final WeightedValue weightedValue = new WeightedValue(value, weight); + + final Node node = data.get(key); + if (node == null) { + return null; + } + for (; ; ) { + final WeightedValue oldWeightedValue = node.get(); + if (!oldWeightedValue.isAlive()) { + return null; + } + if (node.compareAndSet(oldWeightedValue, weightedValue)) { + final int weightedDifference = weight - oldWeightedValue.weight; + if (weightedDifference == 0) { + afterRead(node); + } else { + afterWrite(new UpdateTask(node, weightedDifference)); + } + return oldWeightedValue.value; + } + } + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + checkNotNull(key); + checkNotNull(oldValue); + checkNotNull(newValue); + + final int weight = weigher.weightOf(key, newValue); + final WeightedValue newWeightedValue = new WeightedValue(newValue, weight); + + final Node node = data.get(key); + if (node == null) { + return false; + } + for (; ; ) { + final WeightedValue weightedValue = node.get(); + if (!weightedValue.isAlive() || !weightedValue.contains(oldValue)) { + return false; + } + if (node.compareAndSet(weightedValue, newWeightedValue)) { + final int weightedDifference = weight - weightedValue.weight; + if (weightedDifference == 0) { + afterRead(node); + } else { + afterWrite(new UpdateTask(node, weightedDifference)); + } + return true; + } + } + } + + @Override + public Set keySet() { + final Set ks = keySet; + return (ks == null) ? (keySet = new KeySet()) : ks; + } + + /** + * Returns a unmodifiable snapshot {@link Set} view of the keys contained in this map. The set's + * iterator returns the keys whose order of iteration is the ascending order in which its entries + * are considered eligible for retention, from the least-likely to be retained to the most-likely. + * + *

    Beware that, unlike in {@link #keySet()}, obtaining the set is NOT a constant-time + * operation. Because of the asynchronous nature of the page replacement policy, determining the + * retention ordering requires a traversal of the keys. + * + * @return an ascending snapshot view of the keys in this map + */ + public Set ascendingKeySet() { + return ascendingKeySetWithLimit(Integer.MAX_VALUE); + } + + /** + * Returns an unmodifiable snapshot {@link Set} view of the keys contained in this map. The set's + * iterator returns the keys whose order of iteration is the ascending order in which its entries + * are considered eligible for retention, from the least-likely to be retained to the most-likely. + * + *

    Beware that, unlike in {@link #keySet()}, obtaining the set is NOT a constant-time + * operation. Because of the asynchronous nature of the page replacement policy, determining the + * retention ordering requires a traversal of the keys. + * + * @param limit the maximum size of the returned set + * @return a ascending snapshot view of the keys in this map + * @throws IllegalArgumentException if the limit is negative + */ + public Set ascendingKeySetWithLimit(int limit) { + return orderedKeySet(true, limit); + } + + /** + * Returns an unmodifiable snapshot {@link Set} view of the keys contained in this map. The set's + * iterator returns the keys whose order of iteration is the descending order in which its entries + * are considered eligible for retention, from the most-likely to be retained to the least-likely. + * + *

    Beware that, unlike in {@link #keySet()}, obtaining the set is NOT a constant-time + * operation. Because of the asynchronous nature of the page replacement policy, determining the + * retention ordering requires a traversal of the keys. + * + * @return a descending snapshot view of the keys in this map + */ + public Set descendingKeySet() { + return descendingKeySetWithLimit(Integer.MAX_VALUE); + } + + /** + * Returns an unmodifiable snapshot {@link Set} view of the keys contained in this map. The set's + * iterator returns the keys whose order of iteration is the descending order in which its entries + * are considered eligible for retention, from the most-likely to be retained to the least-likely. + * + *

    Beware that, unlike in {@link #keySet()}, obtaining the set is NOT a constant-time + * operation. Because of the asynchronous nature of the page replacement policy, determining the + * retention ordering requires a traversal of the keys. + * + * @param limit the maximum size of the returned set + * @return a descending snapshot view of the keys in this map + * @throws IllegalArgumentException if the limit is negative + */ + public Set descendingKeySetWithLimit(int limit) { + return orderedKeySet(false, limit); + } + + Set orderedKeySet(boolean ascending, int limit) { + checkArgument(limit >= 0); + evictionLock.lock(); + try { + drainBuffers(); + + final int initialCapacity = + (weigher == Weighers.entrySingleton()) ? Math.min(limit, (int) weightedSize()) : 16; + final Set keys = new LinkedHashSet(initialCapacity); + final Iterator> iterator = + ascending ? evictionDeque.iterator() : evictionDeque.descendingIterator(); + while (iterator.hasNext() && (limit > keys.size())) { + keys.add(iterator.next().key); + } + return unmodifiableSet(keys); + } finally { + evictionLock.unlock(); + } + } + + @Override + public Collection values() { + final Collection vs = values; + return (vs == null) ? (values = new Values()) : vs; + } + + @Override + public Set> entrySet() { + final Set> es = entrySet; + return (es == null) ? (entrySet = new EntrySet()) : es; + } + + /** + * Returns an unmodifiable snapshot {@link Map} view of the mappings contained in this map. The + * map's collections return the mappings whose order of iteration is the ascending order in which + * its entries are considered eligible for retention, from the least-likely to be retained to the + * most-likely. + * + *

    Beware that obtaining the mappings is NOT a constant-time operation. Because of the + * asynchronous nature of the page replacement policy, determining the retention ordering requires + * a traversal of the entries. + * + * @return a ascending snapshot view of this map + */ + public Map ascendingMap() { + return ascendingMapWithLimit(Integer.MAX_VALUE); + } + + /** + * Returns an unmodifiable snapshot {@link Map} view of the mappings contained in this map. The + * map's collections return the mappings whose order of iteration is the ascending order in which + * its entries are considered eligible for retention, from the least-likely to be retained to the + * most-likely. + * + *

    Beware that obtaining the mappings is NOT a constant-time operation. Because of the + * asynchronous nature of the page replacement policy, determining the retention ordering requires + * a traversal of the entries. + * + * @param limit the maximum size of the returned map + * @return a ascending snapshot view of this map + * @throws IllegalArgumentException if the limit is negative + */ + public Map ascendingMapWithLimit(int limit) { + return orderedMap(true, limit); + } + + /** + * Returns an unmodifiable snapshot {@link Map} view of the mappings contained in this map. The + * map's collections return the mappings whose order of iteration is the descending order in which + * its entries are considered eligible for retention, from the most-likely to be retained to the + * least-likely. + * + *

    Beware that obtaining the mappings is NOT a constant-time operation. Because of the + * asynchronous nature of the page replacement policy, determining the retention ordering requires + * a traversal of the entries. + * + * @return a descending snapshot view of this map + */ + public Map descendingMap() { + return descendingMapWithLimit(Integer.MAX_VALUE); + } + + /** + * Returns an unmodifiable snapshot {@link Map} view of the mappings contained in this map. The + * map's collections return the mappings whose order of iteration is the descending order in which + * its entries are considered eligible for retention, from the most-likely to be retained to the + * least-likely. + * + *

    Beware that obtaining the mappings is NOT a constant-time operation. Because of the + * asynchronous nature of the page replacement policy, determining the retention ordering requires + * a traversal of the entries. + * + * @param limit the maximum size of the returned map + * @return a descending snapshot view of this map + * @throws IllegalArgumentException if the limit is negative + */ + public Map descendingMapWithLimit(int limit) { + return orderedMap(false, limit); + } + + Map orderedMap(boolean ascending, int limit) { + checkArgument(limit >= 0); + evictionLock.lock(); + try { + drainBuffers(); + + final int initialCapacity = + (weigher == Weighers.entrySingleton()) ? Math.min(limit, (int) weightedSize()) : 16; + final Map map = new LinkedHashMap(initialCapacity); + final Iterator> iterator = + ascending ? evictionDeque.iterator() : evictionDeque.descendingIterator(); + while (iterator.hasNext() && (limit > map.size())) { + Node node = iterator.next(); + map.put(node.key, node.getValue()); + } + return unmodifiableMap(map); + } finally { + evictionLock.unlock(); + } + } + + /** The draining status of the buffers. */ + enum DrainStatus { + + /** A drain is not taking place. */ + IDLE { + @Override + boolean shouldDrainBuffers(boolean delayable) { + return !delayable; + } + }, + + /** A drain is required due to a pending write modification. */ + REQUIRED { + @Override + boolean shouldDrainBuffers(boolean delayable) { + return true; + } + }, + + /** A drain is in progress. */ + PROCESSING { + @Override + boolean shouldDrainBuffers(boolean delayable) { + return false; + } + }; + + /** + * Determines whether the buffers should be drained. + * + * @param delayable if a drain should be delayed until required + * @return if a drain should be attempted + */ + abstract boolean shouldDrainBuffers(boolean delayable); + } + + /** A value, its weight, and the entry's status. */ + @Immutable + static final class WeightedValue { + final int weight; + final V value; + + WeightedValue(V value, int weight) { + this.weight = weight; + this.value = value; + } + + boolean contains(Object o) { + return (o == value) || value.equals(o); + } + + /** If the entry is available in the hash-table and page replacement policy. */ + boolean isAlive() { + return weight > 0; + } + + /** + * If the entry was removed from the hash-table and is awaiting removal from the page + * replacement policy. + */ + boolean isRetired() { + return weight < 0; + } + + /** If the entry was removed from the hash-table and the page replacement policy. */ + boolean isDead() { + return weight == 0; + } + } + + /** + * A node contains the key, the weighted value, and the linkage pointers on the page-replacement + * algorithm's data structures. + */ + @SuppressWarnings("serial") + static final class Node extends AtomicReference> + implements Linked> { + final K key; + + @GuardedBy("evictionLock") + Node prev; + + @GuardedBy("evictionLock") + Node next; + + /** Creates a new, unlinked node. */ + Node(K key, WeightedValue weightedValue) { + super(weightedValue); + this.key = key; + } + + @Override + @GuardedBy("evictionLock") + public Node getPrevious() { + return prev; + } + + @Override + @GuardedBy("evictionLock") + public void setPrevious(Node prev) { + this.prev = prev; + } + + @Override + @GuardedBy("evictionLock") + public Node getNext() { + return next; + } + + @Override + @GuardedBy("evictionLock") + public void setNext(Node next) { + this.next = next; + } + + /** Retrieves the value held by the current WeightedValue. */ + V getValue() { + return get().value; + } + } + + /** An adapter to safely externalize the keys. */ + final class KeySet extends AbstractSet { + final ConcurrentLinkedHashMap map = ConcurrentLinkedHashMap.this; + + @Override + public int size() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public boolean contains(Object obj) { + return containsKey(obj); + } + + @Override + public boolean remove(Object obj) { + return (map.remove(obj) != null); + } + + @Override + public Object[] toArray() { + return map.data.keySet().toArray(); + } + + @Override + public T[] toArray(T[] array) { + return map.data.keySet().toArray(array); + } + } + + /** An adapter to safely externalize the key iterator. */ + final class KeyIterator implements Iterator { + final Iterator iterator = data.keySet().iterator(); + K current; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + current = iterator.next(); + return current; + } + + @Override + public void remove() { + checkState(current != null); + ConcurrentLinkedHashMap.this.remove(current); + current = null; + } + } + + /** An adapter to safely externalize the values. */ + final class Values extends AbstractCollection { + + @Override + public int size() { + return ConcurrentLinkedHashMap.this.size(); + } + + @Override + public void clear() { + ConcurrentLinkedHashMap.this.clear(); + } + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + } + + /** An adapter to safely externalize the value iterator. */ + final class ValueIterator implements Iterator { + final Iterator> iterator = data.values().iterator(); + Node current; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public V next() { + current = iterator.next(); + return current.getValue(); + } + + @Override + public void remove() { + checkState(current != null); + ConcurrentLinkedHashMap.this.remove(current.key); + current = null; + } + } + + /** An adapter to safely externalize the entries. */ + final class EntrySet extends AbstractSet> { + final ConcurrentLinkedHashMap map = ConcurrentLinkedHashMap.this; + + @Override + public int size() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object obj) { + if (!(obj instanceof Entry)) { + return false; + } + Entry entry = (Entry) obj; + Node node = map.data.get(entry.getKey()); + return (node != null) && (node.getValue().equals(entry.getValue())); + } + + @Override + public boolean add(Entry entry) { + return (map.putIfAbsent(entry.getKey(), entry.getValue()) == null); + } + + @Override + public boolean remove(Object obj) { + if (!(obj instanceof Entry)) { + return false; + } + Entry entry = (Entry) obj; + return map.remove(entry.getKey(), entry.getValue()); + } + } + + /** An adapter to safely externalize the entry iterator. */ + final class EntryIterator implements Iterator> { + final Iterator> iterator = data.values().iterator(); + Node current; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Entry next() { + current = iterator.next(); + return new WriteThroughEntry(current); + } + + @Override + public void remove() { + checkState(current != null); + ConcurrentLinkedHashMap.this.remove(current.key); + current = null; + } + } + + /** An entry that allows updates to write through to the map. */ + final class WriteThroughEntry extends SimpleEntry { + static final long serialVersionUID = 1; + + WriteThroughEntry(Node node) { + super(node.key, node.getValue()); + } + + @Override + public V setValue(V value) { + put(getKey(), value); + return super.setValue(value); + } + + Object writeReplace() { + return new SimpleEntry(this); + } + } + + /** A weigher that enforces that the weight falls within a valid range. */ + static final class BoundedEntryWeigher implements EntryWeigher, Serializable { + static final long serialVersionUID = 1; + final EntryWeigher weigher; + + BoundedEntryWeigher(EntryWeigher weigher) { + checkNotNull(weigher); + this.weigher = weigher; + } + + @Override + public int weightOf(K key, V value) { + int weight = weigher.weightOf(key, value); + checkArgument(weight >= 1); + return weight; + } + + Object writeReplace() { + return weigher; + } + } + + /** A queue that discards all additions and is always empty. */ + static final class DiscardingQueue extends AbstractQueue { + @Override + public boolean add(Object e) { + return true; + } + + @Override + public boolean offer(Object e) { + return true; + } + + @Override + public Object poll() { + return null; + } + + @Override + public Object peek() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public Iterator iterator() { + return emptyList().iterator(); + } + } + + /** A listener that ignores all notifications. */ + enum DiscardingListener implements EvictionListener { + INSTANCE; + + @Override + public void onEviction(Object key, Object value) {} + } + + /* ---------------- Serialization Support -------------- */ + + static final long serialVersionUID = 1; + + Object writeReplace() { + return new SerializationProxy(this); + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Proxy required"); + } + + /** + * A proxy that is serialized instead of the map. The page-replacement algorithm's data structures + * are not serialized so the deserialized instance contains only the entries. This is acceptable + * as caches hold transient data that is recomputable and serialization would tend to be used as a + * fast warm-up process. + */ + static final class SerializationProxy implements Serializable { + final EntryWeigher weigher; + final EvictionListener listener; + final int concurrencyLevel; + final Map data; + final long capacity; + + SerializationProxy(ConcurrentLinkedHashMap map) { + concurrencyLevel = map.concurrencyLevel; + data = new HashMap(map); + capacity = map.capacity.get(); + listener = map.listener; + weigher = map.weigher; + } + + Object readResolve() { + ConcurrentLinkedHashMap map = + new Builder() + .concurrencyLevel(concurrencyLevel) + .maximumWeightedCapacity(capacity) + .listener(listener) + .weigher(weigher) + .build(); + map.putAll(data); + return map; + } + + static final long serialVersionUID = 1; + } + + /* ---------------- Builder -------------- */ + + /** + * A builder that creates {@link ConcurrentLinkedHashMap} instances. It provides a flexible + * approach for constructing customized instances with a named parameter syntax. It can be used in + * the following manner: + * + *
    {@code
    +   * ConcurrentMap> graph = new Builder>()
    +   *     .maximumWeightedCapacity(5000)
    +   *     .weigher(Weighers.set())
    +   *     .build();
    +   * }
    + */ + public static final class Builder { + static final int DEFAULT_CONCURRENCY_LEVEL = 16; + static final int DEFAULT_INITIAL_CAPACITY = 16; + + EvictionListener listener; + EntryWeigher weigher; + + int concurrencyLevel; + int initialCapacity; + long capacity; + + @SuppressWarnings("unchecked") + public Builder() { + capacity = -1; + weigher = Weighers.entrySingleton(); + initialCapacity = DEFAULT_INITIAL_CAPACITY; + concurrencyLevel = DEFAULT_CONCURRENCY_LEVEL; + listener = (EvictionListener) DiscardingListener.INSTANCE; + } + + /** + * Specifies the initial capacity of the hash table (default 16). This is the number of + * key-value pairs that the hash table can hold before a resize operation is required. + * + * @param initialCapacity the initial capacity used to size the hash table to accommodate this + * many entries. + * @throws IllegalArgumentException if the initialCapacity is negative + */ + public Builder initialCapacity(int initialCapacity) { + checkArgument(initialCapacity >= 0); + this.initialCapacity = initialCapacity; + return this; + } + + /** + * Specifies the maximum weighted capacity to coerce the map to and may exceed it temporarily. + * + * @param capacity the weighted threshold to bound the map by + * @throws IllegalArgumentException if the maximumWeightedCapacity is negative + */ + public Builder maximumWeightedCapacity(long capacity) { + checkArgument(capacity >= 0); + this.capacity = capacity; + return this; + } + + /** + * Specifies the estimated number of concurrently updating threads. The implementation performs + * internal sizing to try to accommodate this many threads (default 16). + * + * @param concurrencyLevel the estimated number of concurrently updating threads + * @throws IllegalArgumentException if the concurrencyLevel is less than or equal to zero + */ + public Builder concurrencyLevel(int concurrencyLevel) { + checkArgument(concurrencyLevel > 0); + this.concurrencyLevel = concurrencyLevel; + return this; + } + + /** + * Specifies an optional listener that is registered for notification when an entry is evicted. + * + * @param listener the object to forward evicted entries to + * @throws NullPointerException if the listener is null + */ + public Builder listener(EvictionListener listener) { + checkNotNull(listener); + this.listener = listener; + return this; + } + + /** + * Specifies an algorithm to determine how many the units of capacity a value consumes. The + * default algorithm bounds the map by the number of key-value pairs by giving each entry a + * weight of 1. + * + * @param weigher the algorithm to determine a value's weight + * @throws NullPointerException if the weigher is null + */ + public Builder weigher(Weigher weigher) { + this.weigher = + (weigher == Weighers.singleton()) + ? Weighers.entrySingleton() + : new BoundedEntryWeigher(Weighers.asEntryWeigher(weigher)); + return this; + } + + /** + * Specifies an algorithm to determine how many the units of capacity an entry consumes. The + * default algorithm bounds the map by the number of key-value pairs by giving each entry a + * weight of 1. + * + * @param weigher the algorithm to determine a entry's weight + * @throws NullPointerException if the weigher is null + */ + public Builder weigher(EntryWeigher weigher) { + this.weigher = + (weigher == Weighers.entrySingleton()) + ? Weighers.entrySingleton() + : new BoundedEntryWeigher(weigher); + return this; + } + + /** + * Creates a new {@link ConcurrentLinkedHashMap} instance. + * + * @throws IllegalStateException if the maximum weighted capacity was not set + */ + public ConcurrentLinkedHashMap build() { + checkState(capacity >= 0); + return new ConcurrentLinkedHashMap(this); + } + } +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EntryWeigher.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EntryWeigher.java new file mode 100644 index 000000000000..dee9b739030e --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EntryWeigher.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * A class that can determine the weight of an entry. The total weight threshold is used to + * determine when an eviction is required. + * + * @author ben.manes@gmail.com (Ben Manes) + * @see + * http://code.google.com/p/concurrentlinkedhashmap/ + */ +@ThreadSafe +public interface EntryWeigher { + + /** + * Measures an entry's weight to determine how many units of capacity that the key and value + * consumes. An entry must consume a minimum of one unit. + * + * @param key the key to weigh + * @param value the value to weigh + * @return the entry's weight + */ + int weightOf(K key, V value); +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EvictionListener.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EvictionListener.java new file mode 100644 index 000000000000..71eebb9a5dce --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EvictionListener.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * A listener registered for notification when an entry is evicted. An instance may be called + * concurrently by multiple threads to process entries. An implementation should avoid performing + * blocking calls or synchronizing on shared resources. + * + *

    The listener is invoked by {@link ConcurrentLinkedHashMap} on a caller's thread and will not + * block other threads from operating on the map. An implementation should be aware that the + * caller's thread will not expect long execution times or failures as a side effect of the listener + * being notified. Execution safety and a fast turn around time can be achieved by performing the + * operation asynchronously, such as by submitting a task to an {@link + * java.util.concurrent.ExecutorService}. + * + * @author ben.manes@gmail.com (Ben Manes) + * @see + * http://code.google.com/p/concurrentlinkedhashmap/ + */ +@ThreadSafe +public interface EvictionListener { + + /** + * A call-back notification that the entry was evicted. + * + * @param key the entry's key + * @param value the entry's value + */ + void onEviction(K key, V value); +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LICENSE b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LICENSE new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LinkedDeque.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LinkedDeque.java new file mode 100644 index 000000000000..e8198ee7cafd --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LinkedDeque.java @@ -0,0 +1,432 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.NoSuchElementException; +import javax.annotation.concurrent.NotThreadSafe; + +/** + * Linked list implementation of the {@link Deque} interface where the link pointers are tightly + * integrated with the element. Linked deques have no capacity restrictions; they grow as necessary + * to support usage. They are not thread-safe; in the absence of external synchronization, they do + * not support concurrent access by multiple threads. Null elements are prohibited. + * + *

    Most LinkedDeque operations run in constant time by assuming that the {@link Linked} + * parameter is associated with the deque instance. Any usage that violates this assumption will + * result in non-deterministic behavior. + * + *

    The iterators returned by this class are not fail-fast: If the deque is + * modified at any time after the iterator is created, the iterator will be in an unknown state. + * Thus, in the face of concurrent modification, the iterator risks arbitrary, non-deterministic + * behavior at an undetermined time in the future. + * + * @author ben.manes@gmail.com (Ben Manes) + * @param the type of elements held in this collection + * @see + * http://code.google.com/p/concurrentlinkedhashmap/ + */ +@NotThreadSafe +final class LinkedDeque> extends AbstractCollection implements Deque { + + // This class provides a doubly-linked list that is optimized for the virtual + // machine. The first and last elements are manipulated instead of a slightly + // more convenient sentinel element to avoid the insertion of null checks with + // NullPointerException throws in the byte code. The links to a removed + // element are cleared to help a generational garbage collector if the + // discarded elements inhabit more than one generation. + + /** Pointer to first node. Invariant: (first == null && last == null) || (first.prev == null) */ + E first; + + /** Pointer to last node. Invariant: (first == null && last == null) || (last.next == null) */ + E last; + + /** + * Links the element to the front of the deque so that it becomes the first element. + * + * @param e the unlinked element + */ + void linkFirst(final E e) { + final E f = first; + first = e; + + if (f == null) { + last = e; + } else { + f.setPrevious(e); + e.setNext(f); + } + } + + /** + * Links the element to the back of the deque so that it becomes the last element. + * + * @param e the unlinked element + */ + void linkLast(final E e) { + final E l = last; + last = e; + + if (l == null) { + first = e; + } else { + l.setNext(e); + e.setPrevious(l); + } + } + + /** Unlinks the non-null first element. */ + E unlinkFirst() { + final E f = first; + final E next = f.getNext(); + f.setNext(null); + + first = next; + if (next == null) { + last = null; + } else { + next.setPrevious(null); + } + return f; + } + + /** Unlinks the non-null last element. */ + E unlinkLast() { + final E l = last; + final E prev = l.getPrevious(); + l.setPrevious(null); + last = prev; + if (prev == null) { + first = null; + } else { + prev.setNext(null); + } + return l; + } + + /** Unlinks the non-null element. */ + void unlink(E e) { + final E prev = e.getPrevious(); + final E next = e.getNext(); + + if (prev == null) { + first = next; + } else { + prev.setNext(next); + e.setPrevious(null); + } + + if (next == null) { + last = prev; + } else { + next.setPrevious(prev); + e.setNext(null); + } + } + + @Override + public boolean isEmpty() { + return (first == null); + } + + void checkNotEmpty() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + } + + /** + * {@inheritDoc} + * + *

    Beware that, unlike in most collections, this method is NOT a constant-time + * operation. + */ + @Override + public int size() { + int size = 0; + for (E e = first; e != null; e = e.getNext()) { + size++; + } + return size; + } + + @Override + public void clear() { + for (E e = first; e != null; ) { + E next = e.getNext(); + e.setPrevious(null); + e.setNext(null); + e = next; + } + first = last = null; + } + + @Override + public boolean contains(Object o) { + return (o instanceof Linked) && contains((Linked) o); + } + + // A fast-path containment check + boolean contains(Linked e) { + return (e.getPrevious() != null) || (e.getNext() != null) || (e == first); + } + + /** + * Moves the element to the front of the deque so that it becomes the first element. + * + * @param e the linked element + */ + public void moveToFront(E e) { + if (e != first) { + unlink(e); + linkFirst(e); + } + } + + /** + * Moves the element to the back of the deque so that it becomes the last element. + * + * @param e the linked element + */ + public void moveToBack(E e) { + if (e != last) { + unlink(e); + linkLast(e); + } + } + + @Override + public E peek() { + return peekFirst(); + } + + @Override + public E peekFirst() { + return first; + } + + @Override + public E peekLast() { + return last; + } + + @Override + public E getFirst() { + checkNotEmpty(); + return peekFirst(); + } + + @Override + public E getLast() { + checkNotEmpty(); + return peekLast(); + } + + @Override + public E element() { + return getFirst(); + } + + @Override + public boolean offer(E e) { + return offerLast(e); + } + + @Override + public boolean offerFirst(E e) { + if (contains(e)) { + return false; + } + linkFirst(e); + return true; + } + + @Override + public boolean offerLast(E e) { + if (contains(e)) { + return false; + } + linkLast(e); + return true; + } + + @Override + public boolean add(E e) { + return offerLast(e); + } + + @Override + public void addFirst(E e) { + if (!offerFirst(e)) { + throw new IllegalArgumentException(); + } + } + + @Override + public void addLast(E e) { + if (!offerLast(e)) { + throw new IllegalArgumentException(); + } + } + + @Override + public E poll() { + return pollFirst(); + } + + @Override + public E pollFirst() { + return isEmpty() ? null : unlinkFirst(); + } + + @Override + public E pollLast() { + return isEmpty() ? null : unlinkLast(); + } + + @Override + public E remove() { + return removeFirst(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + return (o instanceof Linked) && remove((E) o); + } + + // A fast-path removal + boolean remove(E e) { + if (contains(e)) { + unlink(e); + return true; + } + return false; + } + + @Override + public E removeFirst() { + checkNotEmpty(); + return pollFirst(); + } + + @Override + public boolean removeFirstOccurrence(Object o) { + return remove(o); + } + + @Override + public E removeLast() { + checkNotEmpty(); + return pollLast(); + } + + @Override + public boolean removeLastOccurrence(Object o) { + return remove(o); + } + + @Override + public boolean removeAll(Collection c) { + boolean modified = false; + for (Object o : c) { + modified |= remove(o); + } + return modified; + } + + @Override + public void push(E e) { + addFirst(e); + } + + @Override + public E pop() { + return removeFirst(); + } + + @Override + public Iterator iterator() { + return new AbstractLinkedIterator(first) { + @Override + E computeNext() { + return cursor.getNext(); + } + }; + } + + @Override + public Iterator descendingIterator() { + return new AbstractLinkedIterator(last) { + @Override + E computeNext() { + return cursor.getPrevious(); + } + }; + } + + abstract class AbstractLinkedIterator implements Iterator { + E cursor; + + /** + * Creates an iterator that can can traverse the deque. + * + * @param start the initial element to begin traversal from + */ + AbstractLinkedIterator(E start) { + cursor = start; + } + + @Override + public boolean hasNext() { + return (cursor != null); + } + + @Override + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + E e = cursor; + cursor = computeNext(); + return e; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** Retrieves the next element to traverse to or null if there are no more elements. */ + abstract E computeNext(); + } +} + +/** An element that is linked on the {@link Deque}. */ +interface Linked> { + + /** + * Retrieves the previous element or null if either the element is unlinked or the first + * element on the deque. + */ + T getPrevious(); + + /** Sets the previous element or null if there is no link. */ + void setPrevious(T prev); + + /** + * Retrieves the next element or null if either the element is unlinked or the last + * element on the deque. + */ + T getNext(); + + /** Sets the next element or null if there is no link. */ + void setNext(T next); +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/NOTICE b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/NOTICE new file mode 100644 index 000000000000..e1cedae49576 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/NOTICE @@ -0,0 +1,7 @@ +ConcurrentLinkedHashMap +Copyright 2008, Ben Manes +Copyright 2010, Google Inc. + +Some alternate data structures provided by JSR-166e +from http://gee.cs.oswego.edu/dl/concurrency-interest/. +Written by Doug Lea and released as Public Domain. diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weigher.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weigher.java new file mode 100644 index 000000000000..bc7729e87727 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weigher.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * A class that can determine the weight of a value. The total weight threshold is used to determine + * when an eviction is required. + * + * @author ben.manes@gmail.com (Ben Manes) + * @see + * http://code.google.com/p/concurrentlinkedhashmap/ + */ +@ThreadSafe +public interface Weigher { + + /** + * Measures an object's weight to determine how many units of capacity that the value consumes. A + * value must consume a minimum of one unit. + * + * @param value the object to weigh + * @return the object's weight + */ + int weightOf(V value); +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weighers.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weighers.java new file mode 100644 index 000000000000..af9b42ba8aa3 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weighers.java @@ -0,0 +1,263 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; + +import static io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap.checkNotNull; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A common set of {@link Weigher} and {@link EntryWeigher} implementations. + * + * @author ben.manes@gmail.com (Ben Manes) + * @see + * http://code.google.com/p/concurrentlinkedhashmap/ + */ +public final class Weighers { + + private Weighers() { + throw new AssertionError(); + } + + /** + * A entry weigher backed by the specified weigher. The weight of the value determines the weight + * of the entry. + * + * @param weigher the weigher to be "wrapped" in a entry weigher. + * @return A entry weigher view of the specified weigher. + */ + public static EntryWeigher asEntryWeigher(final Weigher weigher) { + return (weigher == singleton()) + ? Weighers.entrySingleton() + : new EntryWeigherView(weigher); + } + + /** + * A weigher where an entry has a weight of 1. A map bounded with this weigher will evict + * when the number of key-value pairs exceeds the capacity. + * + * @return A weigher where a value takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static EntryWeigher entrySingleton() { + return (EntryWeigher) SingletonEntryWeigher.INSTANCE; + } + + /** + * A weigher where a value has a weight of 1. A map bounded with this weigher will evict + * when the number of key-value pairs exceeds the capacity. + * + * @return A weigher where a value takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher singleton() { + return (Weigher) SingletonWeigher.INSTANCE; + } + + /** + * A weigher where the value is a byte array and its weight is the number of bytes. A map bounded + * with this weigher will evict when the number of bytes exceeds the capacity rather than the + * number of key-value pairs in the map. This allows for restricting the capacity based on the + * memory-consumption and is primarily for usage by dedicated caching servers that hold the + * serialized data. + * + *

    A value with a weight of 0 will be rejected by the map. If a value with this weight + * can occur then the caller should eagerly evaluate the value and treat it as a removal + * operation. Alternatively, a custom weigher may be specified on the map to assign an empty value + * a positive weight. + * + * @return A weigher where each byte takes one unit of capacity. + */ + public static Weigher byteArray() { + return ByteArrayWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link Iterable} and its weight is the number of elements. This + * weigher only should be used when the alternative {@link #collection()} weigher cannot be, as + * evaluation takes O(n) time. A map bounded with this weigher will evict when the total number of + * elements exceeds the capacity rather than the number of key-value pairs in the map. + * + *

    A value with a weight of 0 will be rejected by the map. If a value with this weight + * can occur then the caller should eagerly evaluate the value and treat it as a removal + * operation. Alternatively, a custom weigher may be specified on the map to assign an empty value + * a positive weight. + * + * @return A weigher where each element takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> iterable() { + return (Weigher>) (Weigher) IterableWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link Collection} and its weight is the number of elements. A + * map bounded with this weigher will evict when the total number of elements exceeds the capacity + * rather than the number of key-value pairs in the map. + * + *

    A value with a weight of 0 will be rejected by the map. If a value with this weight + * can occur then the caller should eagerly evaluate the value and treat it as a removal + * operation. Alternatively, a custom weigher may be specified on the map to assign an empty value + * a positive weight. + * + * @return A weigher where each element takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> collection() { + return (Weigher>) (Weigher) CollectionWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link List} and its weight is the number of elements. A map + * bounded with this weigher will evict when the total number of elements exceeds the capacity + * rather than the number of key-value pairs in the map. + * + *

    A value with a weight of 0 will be rejected by the map. If a value with this weight + * can occur then the caller should eagerly evaluate the value and treat it as a removal + * operation. Alternatively, a custom weigher may be specified on the map to assign an empty value + * a positive weight. + * + * @return A weigher where each element takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> list() { + return (Weigher>) (Weigher) ListWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link Set} and its weight is the number of elements. A map + * bounded with this weigher will evict when the total number of elements exceeds the capacity + * rather than the number of key-value pairs in the map. + * + *

    A value with a weight of 0 will be rejected by the map. If a value with this weight + * can occur then the caller should eagerly evaluate the value and treat it as a removal + * operation. Alternatively, a custom weigher may be specified on the map to assign an empty value + * a positive weight. + * + * @return A weigher where each element takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> set() { + return (Weigher>) (Weigher) SetWeigher.INSTANCE; + } + + /** + * A weigher where the value is a {@link Map} and its weight is the number of entries. A map + * bounded with this weigher will evict when the total number of entries across all values exceeds + * the capacity rather than the number of key-value pairs in the map. + * + *

    A value with a weight of 0 will be rejected by the map. If a value with this weight + * can occur then the caller should eagerly evaluate the value and treat it as a removal + * operation. Alternatively, a custom weigher may be specified on the map to assign an empty value + * a positive weight. + * + * @return A weigher where each entry takes one unit of capacity. + */ + @SuppressWarnings({"cast", "unchecked"}) + public static Weigher> map() { + return (Weigher>) (Weigher) MapWeigher.INSTANCE; + } + + static final class EntryWeigherView implements EntryWeigher, Serializable { + static final long serialVersionUID = 1; + final Weigher weigher; + + EntryWeigherView(Weigher weigher) { + checkNotNull(weigher); + this.weigher = weigher; + } + + @Override + public int weightOf(K key, V value) { + return weigher.weightOf(value); + } + } + + enum SingletonEntryWeigher implements EntryWeigher { + INSTANCE; + + @Override + public int weightOf(Object key, Object value) { + return 1; + } + } + + enum SingletonWeigher implements Weigher { + INSTANCE; + + @Override + public int weightOf(Object value) { + return 1; + } + } + + enum ByteArrayWeigher implements Weigher { + INSTANCE; + + @Override + public int weightOf(byte[] value) { + return value.length; + } + } + + enum IterableWeigher implements Weigher> { + INSTANCE; + + @Override + public int weightOf(Iterable values) { + if (values instanceof Collection) { + return ((Collection) values).size(); + } + int size = 0; + for (Iterator i = values.iterator(); i.hasNext(); ) { + i.next(); + size++; + } + return size; + } + } + + enum CollectionWeigher implements Weigher> { + INSTANCE; + + @Override + public int weightOf(Collection values) { + return values.size(); + } + } + + enum ListWeigher implements Weigher> { + INSTANCE; + + @Override + public int weightOf(List values) { + return values.size(); + } + } + + enum SetWeigher implements Weigher> { + INSTANCE; + + @Override + public int weightOf(Set values) { + return values.size(); + } + } + + enum MapWeigher implements Weigher> { + INSTANCE; + + @Override + public int weightOf(Map values) { + return values.size(); + } + } +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/package-info.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/package-info.java new file mode 100644 index 000000000000..7b331484ddcf --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/package-info.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011 Google Inc. 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. + */ + +/** + * This package contains an implementation of a bounded {@link java.util.concurrent.ConcurrentMap} + * data structure. + * + *

    {@link io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.Weigher} is a simple + * interface for determining how many units of capacity an entry consumes. Depending on which + * concrete Weigher class is used, an entry may consume a different amount of space within the + * cache. The {@link io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.Weighers} + * class provides utility methods for obtaining the most common kinds of implementations. + * + *

    {@link io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.EvictionListener} + * provides the ability to be notified when an entry is evicted from the map. An eviction occurs + * when the entry was automatically removed due to the map exceeding a capacity threshold. It is not + * called when an entry was explicitly removed. + * + *

    The {@link + * io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap} class + * supplies an efficient, scalable, thread-safe, bounded map. As with the Java Collections + * Framework the "Concurrent" prefix is used to indicate that the map is not governed by a + * single exclusion lock. + * + * @see + * http://code.google.com/p/concurrentlinkedhashmap/ + */ +package io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; From 5e595e03b08419ce4645a868a194491b7cf08626 Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Mon, 22 Nov 2021 09:54:16 +0200 Subject: [PATCH 12/30] Let errorprone ignore vendored CLHM for now --- .../src/main/kotlin/otel.errorprone-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts b/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts index 8eb02a4becf7..08b5a4031df3 100644 --- a/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts +++ b/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts @@ -19,7 +19,7 @@ tasks { disableWarningsInGeneratedCode.set(true) allDisabledChecksAsWarnings.set(true) - excludedPaths.set(".*/build/generated/.*") + excludedPaths.set(".*/build/generated/.*|.*/concurrentlinkedhashmap/.*",) if (System.getenv("CI") == null) { disable("SystemOut") From 257a6ecdb3efe1862a4d439458c9a4884519e629 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 22 Nov 2021 13:13:59 -0800 Subject: [PATCH 13/30] Keep license in java files too --- .../ConcurrentLinkedHashMap.java | 59 ++++++++++++------- .../concurrentlinkedhashmap/EntryWeigher.java | 17 ++++++ .../EvictionListener.java | 17 ++++++ .../concurrentlinkedhashmap/LinkedDeque.java | 17 ++++++ .../concurrentlinkedhashmap/Weigher.java | 17 ++++++ .../concurrentlinkedhashmap/Weighers.java | 19 +++++- 6 files changed, 124 insertions(+), 22 deletions(-) diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java index f8bc7bd5a4fa..2fd1ba9c7c18 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java @@ -3,6 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ +// Includes work from: +/* + * Copyright 2010 Google Inc. 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 io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; import static io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap.DrainStatus.IDLE; @@ -199,15 +216,15 @@ private ConcurrentLinkedHashMap(Builder builder) { // The data store and its maximum capacity concurrencyLevel = builder.concurrencyLevel; capacity = new AtomicLong(Math.min(builder.capacity, MAXIMUM_CAPACITY)); - data = new ConcurrentHashMap>(builder.initialCapacity, 0.75f, concurrencyLevel); + data = new ConcurrentHashMap<>(builder.initialCapacity, 0.75f, concurrencyLevel); // The eviction support weigher = builder.weigher; evictionLock = new ReentrantLock(); weightedSize = new AtomicLong(); - evictionDeque = new LinkedDeque>(); - writeBuffer = new ConcurrentLinkedQueue(); - drainStatus = new AtomicReference(IDLE); + evictionDeque = new LinkedDeque<>(); + writeBuffer = new ConcurrentLinkedQueue<>(); + drainStatus = new AtomicReference<>(IDLE); readBufferReadCount = new long[NUMBER_OF_READ_BUFFERS]; readBufferWriteCount = new AtomicLong[NUMBER_OF_READ_BUFFERS]; @@ -218,7 +235,7 @@ private ConcurrentLinkedHashMap(Builder builder) { readBufferDrainAtWriteCount[i] = new AtomicLong(); readBuffers[i] = new AtomicReference[READ_BUFFER_SIZE]; for (int j = 0; j < READ_BUFFER_SIZE; j++) { - readBuffers[i][j] = new AtomicReference>(); + readBuffers[i][j] = new AtomicReference<>(); } } @@ -227,7 +244,7 @@ private ConcurrentLinkedHashMap(Builder builder) { pendingNotifications = (listener == DiscardingListener.INSTANCE) ? (Queue>) DISCARDING_QUEUE - : new ConcurrentLinkedQueue>(); + : new ConcurrentLinkedQueue<>(); } /** Ensures that the object is not null. */ @@ -471,7 +488,7 @@ void drainWriteBuffer() { */ boolean tryToRetire(Node node, WeightedValue expect) { if (expect.isAlive()) { - final WeightedValue retired = new WeightedValue(expect.value, -expect.weight); + final WeightedValue retired = new WeightedValue<>(expect.value, -expect.weight); return node.compareAndSet(expect, retired); } return false; @@ -489,7 +506,7 @@ void makeRetired(Node node) { if (!current.isAlive()) { return; } - final WeightedValue retired = new WeightedValue(current.value, -current.weight); + final WeightedValue retired = new WeightedValue<>(current.value, -current.weight); if (node.compareAndSet(current, retired)) { return; } @@ -506,7 +523,7 @@ void makeRetired(Node node) { void makeDead(Node node) { for (; ; ) { WeightedValue current = node.get(); - WeightedValue dead = new WeightedValue(current.value, 0); + WeightedValue dead = new WeightedValue<>(current.value, 0); if (node.compareAndSet(current, dead)) { weightedSize.lazySet(weightedSize.get() - Math.abs(current.weight)); return; @@ -696,8 +713,8 @@ V put(K key, V value, boolean onlyIfAbsent) { checkNotNull(value); final int weight = weigher.weightOf(key, value); - final WeightedValue weightedValue = new WeightedValue(value, weight); - final Node node = new Node(key, weightedValue); + final WeightedValue weightedValue = new WeightedValue<>(value, weight); + final Node node = new Node<>(key, weightedValue); for (; ; ) { final Node prior = data.putIfAbsent(node.key, node); @@ -773,7 +790,7 @@ public V replace(K key, V value) { checkNotNull(value); final int weight = weigher.weightOf(key, value); - final WeightedValue weightedValue = new WeightedValue(value, weight); + final WeightedValue weightedValue = new WeightedValue<>(value, weight); final Node node = data.get(key); if (node == null) { @@ -803,7 +820,7 @@ public boolean replace(K key, V oldValue, V newValue) { checkNotNull(newValue); final int weight = weigher.weightOf(key, newValue); - final WeightedValue newWeightedValue = new WeightedValue(newValue, weight); + final WeightedValue newWeightedValue = new WeightedValue<>(newValue, weight); final Node node = data.get(key); if (node == null) { @@ -904,7 +921,7 @@ Set orderedKeySet(boolean ascending, int limit) { final int initialCapacity = (weigher == Weighers.entrySingleton()) ? Math.min(limit, (int) weightedSize()) : 16; - final Set keys = new LinkedHashSet(initialCapacity); + final Set keys = new LinkedHashSet<>(initialCapacity); final Iterator> iterator = ascending ? evictionDeque.iterator() : evictionDeque.descendingIterator(); while (iterator.hasNext() && (limit > keys.size())) { @@ -1004,7 +1021,7 @@ Map orderedMap(boolean ascending, int limit) { final int initialCapacity = (weigher == Weighers.entrySingleton()) ? Math.min(limit, (int) weightedSize()) : 16; - final Map map = new LinkedHashMap(initialCapacity); + final Map map = new LinkedHashMap<>(initialCapacity); final Iterator> iterator = ascending ? evictionDeque.iterator() : evictionDeque.descendingIterator(); while (iterator.hasNext() && (limit > map.size())) { @@ -1333,7 +1350,7 @@ public V setValue(V value) { } Object writeReplace() { - return new SimpleEntry(this); + return new SimpleEntry<>(this); } } @@ -1405,7 +1422,7 @@ public void onEviction(Object key, Object value) {} static final long serialVersionUID = 1; Object writeReplace() { - return new SerializationProxy(this); + return new SerializationProxy<>(this); } private void readObject(ObjectInputStream stream) throws InvalidObjectException { @@ -1427,7 +1444,7 @@ static final class SerializationProxy implements Serializable { SerializationProxy(ConcurrentLinkedHashMap map) { concurrencyLevel = map.concurrencyLevel; - data = new HashMap(map); + data = new HashMap<>(map); capacity = map.capacity.get(); listener = map.listener; weigher = map.weigher; @@ -1545,7 +1562,7 @@ public Builder weigher(Weigher weigher) { this.weigher = (weigher == Weighers.singleton()) ? Weighers.entrySingleton() - : new BoundedEntryWeigher(Weighers.asEntryWeigher(weigher)); + : new BoundedEntryWeigher<>(Weighers.asEntryWeigher(weigher)); return this; } @@ -1561,7 +1578,7 @@ public Builder weigher(EntryWeigher weigher) { this.weigher = (weigher == Weighers.entrySingleton()) ? Weighers.entrySingleton() - : new BoundedEntryWeigher(weigher); + : new BoundedEntryWeigher<>(weigher); return this; } @@ -1572,7 +1589,7 @@ public Builder weigher(EntryWeigher weigher) { */ public ConcurrentLinkedHashMap build() { checkState(capacity >= 0); - return new ConcurrentLinkedHashMap(this); + return new ConcurrentLinkedHashMap<>(this); } } } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EntryWeigher.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EntryWeigher.java index dee9b739030e..cba75b19a42d 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EntryWeigher.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EntryWeigher.java @@ -3,6 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ +// Includes work from: +/* + * Copyright 2012 Google Inc. 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 io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; import javax.annotation.concurrent.ThreadSafe; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EvictionListener.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EvictionListener.java index 71eebb9a5dce..846c977c3d4e 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EvictionListener.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/EvictionListener.java @@ -3,6 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ +// Includes work from: +/* + * Copyright 2010 Google Inc. 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 io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; import javax.annotation.concurrent.ThreadSafe; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LinkedDeque.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LinkedDeque.java index e8198ee7cafd..e69e746acad6 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LinkedDeque.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LinkedDeque.java @@ -3,6 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ +// Includes work from: +/* + * Copyright 2011 Google Inc. 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 io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; import java.util.AbstractCollection; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weigher.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weigher.java index bc7729e87727..3a68b0d63de3 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weigher.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weigher.java @@ -3,6 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ +// Includes work from: +/* + * Copyright 2010 Google Inc. 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 io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; import javax.annotation.concurrent.ThreadSafe; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weighers.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weighers.java index af9b42ba8aa3..98ba4b053efb 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weighers.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/Weighers.java @@ -3,6 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ +// Includes work from: +/* + * Copyright 2010 Google Inc. 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 io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap; import static io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap.checkNotNull; @@ -37,7 +54,7 @@ private Weighers() { public static EntryWeigher asEntryWeigher(final Weigher weigher) { return (weigher == singleton()) ? Weighers.entrySingleton() - : new EntryWeigherView(weigher); + : new EntryWeigherView<>(weigher); } /** From dac1522a3f6fef096f379554e84e99f206229302 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 15:14:54 -0800 Subject: [PATCH 14/30] Convert Netty wrapper cache to VirtualField --- .../netty/common/FutureListenerWrappers.java | 61 ++++++++----------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java index f1e3881505e0..ce71b689c015 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java @@ -11,22 +11,19 @@ import io.netty.util.concurrent.ProgressiveFuture; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.cache.Cache; -import java.lang.ref.WeakReference; +import io.opentelemetry.instrumentation.api.field.VirtualField; public final class FutureListenerWrappers { - // Instead of VirtualField use Cache with weak keys and weak values to store link between original - // listener and wrapper. VirtualField works fine when wrapper is stored in a field on original - // listener, but when listener class is a lambda instead of field it gets stored in a map with - // weak keys where original listener is key and wrapper is value. As wrapper has a strong - // reference to original listener this causes a memory leak. - // Also note that it's ok if the value is collected prior to the key, since this cache is only - // used to remove the wrapped listener from the netty future, and if the value is collected prior - // to the key, that means it's no longer used (referenced) by the netty future anyways. - private static final Cache< - GenericFutureListener>, - WeakReference>>> - wrappers = Cache.weak(); + // important: if this is ever converted to library instrumentation, this will create a memory leak + // because while library implementation of VirtualField maintains a weak reference to its keys, it + // maintains a strong reference to its values, and the wrapper has a strong reference to original + // listener, which will create a memory leak. + // this is not a problem in the javaagent's implementation of VirtualField, since it injects the + // value directly into the key as a field, and so the value is only retained strongly by the key, + // and so they can be collected together. + @SuppressWarnings("rawtypes") + private static final VirtualField wrapperField = + VirtualField.find(GenericFutureListener.class, GenericFutureListener.class); private static final ClassValue shouldWrap = new ClassValue() { @@ -46,32 +43,24 @@ public static boolean shouldWrap(GenericFutureListener> list @SuppressWarnings("unchecked") public static GenericFutureListener wrap( Context context, GenericFutureListener> delegate) { - WeakReference>> resultReference = - wrappers.computeIfAbsent( - delegate, - key -> { - GenericFutureListener> wrapper; - if (delegate instanceof GenericProgressiveFutureListener) { - wrapper = - new WrappedProgressiveFutureListener( - context, (GenericProgressiveFutureListener>) delegate); - } else { - wrapper = - new WrappedFutureListener(context, (GenericFutureListener>) delegate); - } - return new WeakReference<>(wrapper); - }); - return resultReference.get(); + GenericFutureListener> wrapper = wrapperField.get(delegate); + if (wrapper == null) { + if (delegate instanceof GenericProgressiveFutureListener) { + wrapper = + new WrappedProgressiveFutureListener( + context, (GenericProgressiveFutureListener>) delegate); + } else { + wrapper = new WrappedFutureListener(context, (GenericFutureListener>) delegate); + } + wrapperField.set(delegate, wrapper); + } + return wrapper; } + @SuppressWarnings("unchecked") public static GenericFutureListener> getWrapper( GenericFutureListener> delegate) { - WeakReference>> wrapperReference = - wrappers.get(delegate); - if (wrapperReference == null) { - return delegate; - } - GenericFutureListener> wrapper = wrapperReference.get(); + GenericFutureListener> wrapper = wrapperField.get(delegate); return wrapper == null ? delegate : wrapper; } From 6d63815b44ca4721e9ee417f4f18314ee703bd80 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 16:14:38 -0800 Subject: [PATCH 15/30] Work around lambda instrumentation failure Ideally we would ignore instrumenting helper classes... --- .../NettySslInstrumentationHandler.java | 35 +++++++++++++------ .../InstrumentedAddressResolverGroup.java | 20 ++++++++++- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/client/NettySslInstrumentationHandler.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/client/NettySslInstrumentationHandler.java index b52e7c4e6e4a..b63260246ce1 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/client/NettySslInstrumentationHandler.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/client/NettySslInstrumentationHandler.java @@ -8,6 +8,8 @@ import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; import io.opentelemetry.context.Context; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -75,17 +77,7 @@ public void connect( // netty SslHandler starts the handshake after it receives the channelActive() signal; this // happens just after the connection is established // this makes connect() promise a good place to start the SSL handshake span - promise.addListener( - future -> { - // there won't be any SSL handshake if the channel fails to connect - if (!future.isSuccess()) { - return; - } - request = NettySslRequest.create(ctx.channel()); - if (instrumenter.shouldStart(parentContext, request)) { - context = instrumenter.start(parentContext, request); - } - }); + promise.addListener(new StartListener(ctx)); ctx.connect(remoteAddress, localAddress, promise); } @@ -112,4 +104,25 @@ private static Throwable getCause(Object evt) { return null; } } + + private class StartListener implements GenericFutureListener> { + + private final ChannelHandlerContext ctx; + + private StartListener(ChannelHandlerContext ctx) { + this.ctx = ctx; + } + + @Override + public void operationComplete(Future future) { + // there won't be any SSL handshake if the channel fails to connect + if (!future.isSuccess()) { + return; + } + request = NettySslRequest.create(ctx.channel()); + if (instrumenter.shouldStart(parentContext, request)) { + context = instrumenter.start(parentContext, request); + } + } + } } diff --git a/instrumentation/netty/netty-4.1-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/InstrumentedAddressResolverGroup.java b/instrumentation/netty/netty-4.1-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/InstrumentedAddressResolverGroup.java index 3607fce2e83d..8fb635ad550e 100644 --- a/instrumentation/netty/netty-4.1-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/InstrumentedAddressResolverGroup.java +++ b/instrumentation/netty/netty-4.1-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/InstrumentedAddressResolverGroup.java @@ -9,6 +9,7 @@ import io.netty.resolver.AddressResolverGroup; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; import io.opentelemetry.context.Context; import io.opentelemetry.javaagent.instrumentation.netty.common.NettyConnectionRequest; @@ -106,7 +107,7 @@ private Future instrumentResolve( Context context = instrumenter.start(parentContext, request); try { Future future = resolveFunc.get(); - return future.addListener(f -> instrumenter.end(context, request, null, f.cause())); + return future.addListener(new OnEndListener<>(request, context)); } catch (Throwable t) { instrumenter.end(context, request, null, t); throw t; @@ -117,5 +118,22 @@ private Future instrumentResolve( public void close() { delegate.close(); } + + // currently cannot use lambda for this + private class OnEndListener implements GenericFutureListener> { + + private final NettyConnectionRequest request; + private final Context context; + + private OnEndListener(NettyConnectionRequest request, Context context) { + this.request = request; + this.context = context; + } + + @Override + public void operationComplete(Future future) { + instrumenter.end(context, request, null, future.cause()); + } + } } } From 7448cde2f5d1259e12cea450f1da96ea152daf53 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 16:15:13 -0800 Subject: [PATCH 16/30] Revert "Work around lambda instrumentation failure" This reverts commit 6d63815b44ca4721e9ee417f4f18314ee703bd80. --- .../NettySslInstrumentationHandler.java | 35 ++++++------------- .../InstrumentedAddressResolverGroup.java | 20 +---------- 2 files changed, 12 insertions(+), 43 deletions(-) diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/client/NettySslInstrumentationHandler.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/client/NettySslInstrumentationHandler.java index b63260246ce1..b52e7c4e6e4a 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/client/NettySslInstrumentationHandler.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/client/NettySslInstrumentationHandler.java @@ -8,8 +8,6 @@ import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; import io.opentelemetry.context.Context; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -77,7 +75,17 @@ public void connect( // netty SslHandler starts the handshake after it receives the channelActive() signal; this // happens just after the connection is established // this makes connect() promise a good place to start the SSL handshake span - promise.addListener(new StartListener(ctx)); + promise.addListener( + future -> { + // there won't be any SSL handshake if the channel fails to connect + if (!future.isSuccess()) { + return; + } + request = NettySslRequest.create(ctx.channel()); + if (instrumenter.shouldStart(parentContext, request)) { + context = instrumenter.start(parentContext, request); + } + }); ctx.connect(remoteAddress, localAddress, promise); } @@ -104,25 +112,4 @@ private static Throwable getCause(Object evt) { return null; } } - - private class StartListener implements GenericFutureListener> { - - private final ChannelHandlerContext ctx; - - private StartListener(ChannelHandlerContext ctx) { - this.ctx = ctx; - } - - @Override - public void operationComplete(Future future) { - // there won't be any SSL handshake if the channel fails to connect - if (!future.isSuccess()) { - return; - } - request = NettySslRequest.create(ctx.channel()); - if (instrumenter.shouldStart(parentContext, request)) { - context = instrumenter.start(parentContext, request); - } - } - } } diff --git a/instrumentation/netty/netty-4.1-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/InstrumentedAddressResolverGroup.java b/instrumentation/netty/netty-4.1-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/InstrumentedAddressResolverGroup.java index 8fb635ad550e..3607fce2e83d 100644 --- a/instrumentation/netty/netty-4.1-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/InstrumentedAddressResolverGroup.java +++ b/instrumentation/netty/netty-4.1-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/v4_1/InstrumentedAddressResolverGroup.java @@ -9,7 +9,6 @@ import io.netty.resolver.AddressResolverGroup; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; import io.opentelemetry.context.Context; import io.opentelemetry.javaagent.instrumentation.netty.common.NettyConnectionRequest; @@ -107,7 +106,7 @@ private Future instrumentResolve( Context context = instrumenter.start(parentContext, request); try { Future future = resolveFunc.get(); - return future.addListener(new OnEndListener<>(request, context)); + return future.addListener(f -> instrumenter.end(context, request, null, f.cause())); } catch (Throwable t) { instrumenter.end(context, request, null, t); throw t; @@ -118,22 +117,5 @@ private Future instrumentResolve( public void close() { delegate.close(); } - - // currently cannot use lambda for this - private class OnEndListener implements GenericFutureListener> { - - private final NettyConnectionRequest request; - private final Context context; - - private OnEndListener(NettyConnectionRequest request, Context context) { - this.request = request; - this.context = context; - } - - @Override - public void operationComplete(Future future) { - instrumenter.end(context, request, null, future.cause()); - } - } } } From fad0967548ab028c513bbe24b61885a35c5f3e60 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 16:15:23 -0800 Subject: [PATCH 17/30] Revert "Convert Netty wrapper cache to VirtualField" This reverts commit dac1522a3f6fef096f379554e84e99f206229302. --- .../netty/common/FutureListenerWrappers.java | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java index ce71b689c015..f1e3881505e0 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java @@ -11,19 +11,22 @@ import io.netty.util.concurrent.ProgressiveFuture; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.instrumentation.api.field.VirtualField; +import io.opentelemetry.instrumentation.api.cache.Cache; +import java.lang.ref.WeakReference; public final class FutureListenerWrappers { - // important: if this is ever converted to library instrumentation, this will create a memory leak - // because while library implementation of VirtualField maintains a weak reference to its keys, it - // maintains a strong reference to its values, and the wrapper has a strong reference to original - // listener, which will create a memory leak. - // this is not a problem in the javaagent's implementation of VirtualField, since it injects the - // value directly into the key as a field, and so the value is only retained strongly by the key, - // and so they can be collected together. - @SuppressWarnings("rawtypes") - private static final VirtualField wrapperField = - VirtualField.find(GenericFutureListener.class, GenericFutureListener.class); + // Instead of VirtualField use Cache with weak keys and weak values to store link between original + // listener and wrapper. VirtualField works fine when wrapper is stored in a field on original + // listener, but when listener class is a lambda instead of field it gets stored in a map with + // weak keys where original listener is key and wrapper is value. As wrapper has a strong + // reference to original listener this causes a memory leak. + // Also note that it's ok if the value is collected prior to the key, since this cache is only + // used to remove the wrapped listener from the netty future, and if the value is collected prior + // to the key, that means it's no longer used (referenced) by the netty future anyways. + private static final Cache< + GenericFutureListener>, + WeakReference>>> + wrappers = Cache.weak(); private static final ClassValue shouldWrap = new ClassValue() { @@ -43,24 +46,32 @@ public static boolean shouldWrap(GenericFutureListener> list @SuppressWarnings("unchecked") public static GenericFutureListener wrap( Context context, GenericFutureListener> delegate) { - GenericFutureListener> wrapper = wrapperField.get(delegate); - if (wrapper == null) { - if (delegate instanceof GenericProgressiveFutureListener) { - wrapper = - new WrappedProgressiveFutureListener( - context, (GenericProgressiveFutureListener>) delegate); - } else { - wrapper = new WrappedFutureListener(context, (GenericFutureListener>) delegate); - } - wrapperField.set(delegate, wrapper); - } - return wrapper; + WeakReference>> resultReference = + wrappers.computeIfAbsent( + delegate, + key -> { + GenericFutureListener> wrapper; + if (delegate instanceof GenericProgressiveFutureListener) { + wrapper = + new WrappedProgressiveFutureListener( + context, (GenericProgressiveFutureListener>) delegate); + } else { + wrapper = + new WrappedFutureListener(context, (GenericFutureListener>) delegate); + } + return new WeakReference<>(wrapper); + }); + return resultReference.get(); } - @SuppressWarnings("unchecked") public static GenericFutureListener> getWrapper( GenericFutureListener> delegate) { - GenericFutureListener> wrapper = wrapperField.get(delegate); + WeakReference>> wrapperReference = + wrappers.get(delegate); + if (wrapperReference == null) { + return delegate; + } + GenericFutureListener> wrapper = wrapperReference.get(); return wrapper == null ? delegate : wrapper; } From 9ab70f4d2aa52abcdb1a27ed917494cfe1276b69 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 15:30:34 -0800 Subject: [PATCH 18/30] Handle cleared weak values --- .../netty/common/FutureListenerWrappers.java | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java index f1e3881505e0..63d64a891162 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java @@ -15,14 +15,18 @@ import java.lang.ref.WeakReference; public final class FutureListenerWrappers { - // Instead of VirtualField use Cache with weak keys and weak values to store link between original - // listener and wrapper. VirtualField works fine when wrapper is stored in a field on original - // listener, but when listener class is a lambda instead of field it gets stored in a map with - // weak keys where original listener is key and wrapper is value. As wrapper has a strong - // reference to original listener this causes a memory leak. - // Also note that it's ok if the value is collected prior to the key, since this cache is only - // used to remove the wrapped listener from the netty future, and if the value is collected prior - // to the key, that means it's no longer used (referenced) by the netty future anyways. + // note: it's ok if the value is collected prior to the key, since this cache is only used to + // remove the wrapped listener from the netty future, and if the value is collected prior to the + // key, that means it's no longer used (referenced) by the netty future anyways. + // + // also note: this is not using VirtualField in case this is ever converted to library + // instrumentation, because while the library implementation of VirtualField maintains a weak + // reference to its keys, it maintains a strong reference to its values, and in this particular + // case the wrapper listener (value) has a strong reference to original listener (key), which will + // create a memory leak. which is not a problem in the javaagent's implementation of VirtualField, + // since it injects the value directly into the key as a field, and so the value is only retained + // strongly by the key, and so they can be collected together (though currently the tests fail + // when using VirtualField due to ) private static final Cache< GenericFutureListener>, WeakReference>>> @@ -46,22 +50,34 @@ public static boolean shouldWrap(GenericFutureListener> list @SuppressWarnings("unchecked") public static GenericFutureListener wrap( Context context, GenericFutureListener> delegate) { + + // note: not using computeIfAbsent because that leaves window where WeakReference can be + // collected before we have a chance to make (and return) a strong reference to the wrapper + WeakReference>> resultReference = - wrappers.computeIfAbsent( - delegate, - key -> { - GenericFutureListener> wrapper; - if (delegate instanceof GenericProgressiveFutureListener) { - wrapper = - new WrappedProgressiveFutureListener( - context, (GenericProgressiveFutureListener>) delegate); - } else { - wrapper = - new WrappedFutureListener(context, (GenericFutureListener>) delegate); - } - return new WeakReference<>(wrapper); - }); - return resultReference.get(); + wrappers.get(delegate); + + if (resultReference != null) { + GenericFutureListener> wrapper = resultReference.get(); + if (wrapper != null) { + return wrapper; + } + // note that it's ok if the value is collected prior to the key, since this cache is only + // used to remove the wrapped listener from the netty future, and if the value is collected + // prior + // to the key, that means it's no longer used (referenced) by the netty future anyways. + } + + final GenericFutureListener> wrapper; + if (delegate instanceof GenericProgressiveFutureListener) { + wrapper = + new WrappedProgressiveFutureListener( + context, (GenericProgressiveFutureListener>) delegate); + } else { + wrapper = new WrappedFutureListener(context, (GenericFutureListener>) delegate); + } + wrappers.put(delegate, new WeakReference<>(wrapper)); + return wrapper; } public static GenericFutureListener> getWrapper( From 3f3667823185a5b6c95cd3203aa20852574d8c7e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 16:24:38 -0800 Subject: [PATCH 19/30] Fix comment --- .../instrumentation/netty/common/FutureListenerWrappers.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java index 63d64a891162..509e1233bc16 100644 --- a/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java +++ b/instrumentation/netty/netty-4-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/netty/common/FutureListenerWrappers.java @@ -25,8 +25,7 @@ public final class FutureListenerWrappers { // case the wrapper listener (value) has a strong reference to original listener (key), which will // create a memory leak. which is not a problem in the javaagent's implementation of VirtualField, // since it injects the value directly into the key as a field, and so the value is only retained - // strongly by the key, and so they can be collected together (though currently the tests fail - // when using VirtualField due to ) + // strongly by the key, and so they can be collected together. private static final Cache< GenericFutureListener>, WeakReference>>> From 6ebb98b206a602adc864ad2bcadcfadb4da58290 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 16:31:10 -0800 Subject: [PATCH 20/30] Delete instrumentation-api-caching --- instrumentation-api-caching/build.gradle.kts | 44 --------- .../caffeine2/build.gradle.kts | 58 ----------- .../caffeine/cache/CacheImplementations.java | 29 ------ .../caffeine2/cache/BoundedLocalCache.java | 64 ------------ .../caffeine3/build.gradle.kts | 41 -------- .../caffeine/cache/CacheImplementations.java | 29 ------ .../caffeine3/cache/BoundedLocalCache.java | 64 ------------ .../instrumentation/api/caching/Cache.java | 37 ------- .../api/caching/CacheBuilder.java | 74 -------------- .../api/caching/Caffeine2Cache.java | 89 ----------------- .../api/caching/Caffeine3Cache.java | 99 ------------------- .../api/caching/CaffeineCache.java | 33 ------- .../src/test/README.md | 1 - 13 files changed, 662 deletions(-) delete mode 100644 instrumentation-api-caching/build.gradle.kts delete mode 100644 instrumentation-api-caching/caffeine2/build.gradle.kts delete mode 100644 instrumentation-api-caching/caffeine2/src/main/java/com/github/benmanes/caffeine/cache/CacheImplementations.java delete mode 100644 instrumentation-api-caching/caffeine2/src/patch/java/io/opentelemetry/instrumentation/api/internal/shaded/caffeine2/cache/BoundedLocalCache.java delete mode 100644 instrumentation-api-caching/caffeine3/build.gradle.kts delete mode 100644 instrumentation-api-caching/caffeine3/src/main/java/com/github/benmanes/caffeine/cache/CacheImplementations.java delete mode 100644 instrumentation-api-caching/caffeine3/src/patch/java/io/opentelemetry/instrumentation/api/internal/shaded/caffeine3/cache/BoundedLocalCache.java delete mode 100644 instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Cache.java delete mode 100644 instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/CacheBuilder.java delete mode 100644 instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Caffeine2Cache.java delete mode 100644 instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Caffeine3Cache.java delete mode 100644 instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/CaffeineCache.java delete mode 100644 instrumentation-api-caching/src/test/README.md diff --git a/instrumentation-api-caching/build.gradle.kts b/instrumentation-api-caching/build.gradle.kts deleted file mode 100644 index 1e4d6ef3344f..000000000000 --- a/instrumentation-api-caching/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -plugins { - id("com.github.johnrengelman.shadow") - - id("otel.java-conventions") -} - -group = "io.opentelemetry.javaagent" - -sourceSets { - main { - val caffeine2ShadedDeps = project(":instrumentation-api-caching:caffeine2") - output.dir(caffeine2ShadedDeps.file("build/extracted/shadow"), "builtBy" to ":instrumentation-api-caching:caffeine2:extractShadowJar") - } -} - -val shadowInclude by configurations.creating { - isCanBeResolved = true - isCanBeConsumed = false -} - -dependencies { - compileOnly(project(":instrumentation-api-caching:caffeine2", configuration = "shadow")) - compileOnly(project(":instrumentation-api-caching:caffeine3", configuration = "shadow")) - - compileOnly("org.checkerframework:checker-qual:3.14.0") - compileOnly("com.blogspot.mydailyjava:weak-lock-free") - shadowInclude("com.blogspot.mydailyjava:weak-lock-free") -} - -tasks { - shadowJar { - configurations = listOf(shadowInclude) - - relocate("com.blogspot.mydailyjava.weaklockfree", "io.opentelemetry.instrumentation.api.internal.shaded.weaklockfree") - } - - val extractShadowJar by registering(Copy::class) { - dependsOn(shadowJar) - from(zipTree(shadowJar.get().archiveFile)) { - exclude("META-INF/**") - } - into("build/extracted/shadow") - } -} diff --git a/instrumentation-api-caching/caffeine2/build.gradle.kts b/instrumentation-api-caching/caffeine2/build.gradle.kts deleted file mode 100644 index 5d4b31586f35..000000000000 --- a/instrumentation-api-caching/caffeine2/build.gradle.kts +++ /dev/null @@ -1,58 +0,0 @@ -plugins { - id("com.github.johnrengelman.shadow") - - id("otel.java-conventions") -} - -group = "io.opentelemetry.javaagent" - -val shadowInclude by configurations.creating { - isCanBeResolved = true - isCanBeConsumed = false -} - -val caffeine2Version: String by project - -dependencies { - compileOnly("com.github.ben-manes.caffeine:caffeine:$caffeine2Version") - shadowInclude("com.github.ben-manes.caffeine:caffeine:$caffeine2Version") { - exclude("com.google.errorprone", "error_prone_annotations") - exclude("org.checkerframework", "checker-qual") - } -} - -// patch inner class from Caffeine to avoid ForkJoinTask from being loaded too early in the javaagent -val patch by sourceSets.creating { - java {} -} - -tasks { - shadowJar { - configurations = listOf(shadowInclude) - - relocate("com.github.benmanes.caffeine", "io.opentelemetry.instrumentation.api.internal.shaded.caffeine2") - - minimize() - } - - javadoc { - enabled = false - } - - val extractShadowJar by registering(Copy::class) { - dependsOn(shadowJar) - - // replace caffeine class with our patched version - from(zipTree(shadowJar.get().archiveFile)) { - exclude("io/opentelemetry/instrumentation/api/internal/shaded/caffeine2/cache/BoundedLocalCache\$PerformCleanupTask.class") - exclude("META-INF/**") - } - from(patch.output) { - include("io/opentelemetry/instrumentation/api/internal/shaded/caffeine2/cache/BoundedLocalCache\$PerformCleanupTask.class") - } - - into("build/extracted/shadow") - // prevents empty com/github/benmanes/caffeine/cache path from ending up in instrumentation-api - includeEmptyDirs = false - } -} diff --git a/instrumentation-api-caching/caffeine2/src/main/java/com/github/benmanes/caffeine/cache/CacheImplementations.java b/instrumentation-api-caching/caffeine2/src/main/java/com/github/benmanes/caffeine/cache/CacheImplementations.java deleted file mode 100644 index f48459eb550b..000000000000 --- a/instrumentation-api-caching/caffeine2/src/main/java/com/github/benmanes/caffeine/cache/CacheImplementations.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.github.benmanes.caffeine.cache; - -// Caffeine uses reflection to load cache implementations based on parameters specified by a user. -// We use gradle-shadow-plugin to minimize the dependency on Caffeine, but it does not allow -// specifying classes to keep, only artifacts. It's a relatively simple workaround for us to use -// this non-public class to create a static link to the required implementations we use. -final class CacheImplementations { - - // Each type of cache has a cache implementation and a node implementation. - - // Strong keys, strong values, maximum size - SSMS ssms; // cache - PSMS psms; // node - - // Weak keys, strong values, maximum size - WSMS wsms; // cache - FSMS fsms; // node - - // Weak keys, weak values - WI wi; // cache - FW fw; // node - - private CacheImplementations() {} -} diff --git a/instrumentation-api-caching/caffeine2/src/patch/java/io/opentelemetry/instrumentation/api/internal/shaded/caffeine2/cache/BoundedLocalCache.java b/instrumentation-api-caching/caffeine2/src/patch/java/io/opentelemetry/instrumentation/api/internal/shaded/caffeine2/cache/BoundedLocalCache.java deleted file mode 100644 index 44c4405449a0..000000000000 --- a/instrumentation-api-caching/caffeine2/src/patch/java/io/opentelemetry/instrumentation/api/internal/shaded/caffeine2/cache/BoundedLocalCache.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -// Includes work from: -/* - * Copyright 2017 Datadog, Inc. - * - * 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. - */ -/* - * Copyright 2014 Ben Manes. 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 io.opentelemetry.instrumentation.api.internal.shaded.caffeine2.cache; - -import java.lang.ref.WeakReference; - -/** skeleton outer class just for compilation purposes, not included in the final patch. */ -abstract class BoundedLocalCache { - abstract void performCleanUp(Runnable task); - - /** patched to not extend ForkJoinTask as we don't want that class loaded too early. */ - static final class PerformCleanupTask implements Runnable { - private static final long serialVersionUID = 1L; - - final WeakReference> reference; - - PerformCleanupTask(BoundedLocalCache cache) { - reference = new WeakReference<>(cache); - } - - @Override - public void run() { - BoundedLocalCache cache = reference.get(); - if (cache != null) { - cache.performCleanUp(/* ignored */ null); - } - } - } -} diff --git a/instrumentation-api-caching/caffeine3/build.gradle.kts b/instrumentation-api-caching/caffeine3/build.gradle.kts deleted file mode 100644 index a6c3f1e2bc0c..000000000000 --- a/instrumentation-api-caching/caffeine3/build.gradle.kts +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - id("com.github.johnrengelman.shadow") - - id("otel.java-conventions") -} - -group = "io.opentelemetry.javaagent" - -val shadowInclude by configurations.creating { - isCanBeResolved = true - isCanBeConsumed = false -} - -val caffeine3Version: String by project - -dependencies { - compileOnly("com.github.ben-manes.caffeine:caffeine:$caffeine3Version") - shadowInclude("com.github.ben-manes.caffeine:caffeine:$caffeine3Version") { - exclude("com.google.errorprone", "error_prone_annotations") - exclude("org.checkerframework", "checker-qual") - } -} - -// patch inner class from Caffeine to avoid ForkJoinTask from being loaded too early in the javaagent -val patch by sourceSets.creating { - java {} -} - -tasks { - shadowJar { - configurations = listOf(shadowInclude) - - relocate("com.github.benmanes.caffeine", "io.opentelemetry.instrumentation.api.internal.shaded.caffeine3") - - minimize() - } - - javadoc { - enabled = false - } -} diff --git a/instrumentation-api-caching/caffeine3/src/main/java/com/github/benmanes/caffeine/cache/CacheImplementations.java b/instrumentation-api-caching/caffeine3/src/main/java/com/github/benmanes/caffeine/cache/CacheImplementations.java deleted file mode 100644 index f48459eb550b..000000000000 --- a/instrumentation-api-caching/caffeine3/src/main/java/com/github/benmanes/caffeine/cache/CacheImplementations.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.github.benmanes.caffeine.cache; - -// Caffeine uses reflection to load cache implementations based on parameters specified by a user. -// We use gradle-shadow-plugin to minimize the dependency on Caffeine, but it does not allow -// specifying classes to keep, only artifacts. It's a relatively simple workaround for us to use -// this non-public class to create a static link to the required implementations we use. -final class CacheImplementations { - - // Each type of cache has a cache implementation and a node implementation. - - // Strong keys, strong values, maximum size - SSMS ssms; // cache - PSMS psms; // node - - // Weak keys, strong values, maximum size - WSMS wsms; // cache - FSMS fsms; // node - - // Weak keys, weak values - WI wi; // cache - FW fw; // node - - private CacheImplementations() {} -} diff --git a/instrumentation-api-caching/caffeine3/src/patch/java/io/opentelemetry/instrumentation/api/internal/shaded/caffeine3/cache/BoundedLocalCache.java b/instrumentation-api-caching/caffeine3/src/patch/java/io/opentelemetry/instrumentation/api/internal/shaded/caffeine3/cache/BoundedLocalCache.java deleted file mode 100644 index b4e6a5e681e2..000000000000 --- a/instrumentation-api-caching/caffeine3/src/patch/java/io/opentelemetry/instrumentation/api/internal/shaded/caffeine3/cache/BoundedLocalCache.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -// Includes work from: -/* - * Copyright 2017 Datadog, Inc. - * - * 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. - */ -/* - * Copyright 2014 Ben Manes. 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 io.opentelemetry.instrumentation.api.internal.shaded.caffeine3.cache; - -import java.lang.ref.WeakReference; - -/** skeleton outer class just for compilation purposes, not included in the final patch. */ -abstract class BoundedLocalCache { - abstract void performCleanUp(Runnable task); - - /** patched to not extend ForkJoinTask as we don't want that class loaded too early. */ - static final class PerformCleanupTask implements Runnable { - private static final long serialVersionUID = 1L; - - final WeakReference> reference; - - PerformCleanupTask(BoundedLocalCache cache) { - reference = new WeakReference<>(cache); - } - - @Override - public void run() { - BoundedLocalCache cache = reference.get(); - if (cache != null) { - cache.performCleanUp(/* ignored */ null); - } - } - } -} diff --git a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Cache.java b/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Cache.java deleted file mode 100644 index 549144dce4e3..000000000000 --- a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Cache.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.caching; - -import java.util.function.Function; -import javax.annotation.Nullable; - -/** A cache from keys to values. */ -interface Cache { - - /** Returns a new {@link CacheBuilder} to configure a {@link Cache}. */ - static CacheBuilder builder() { - return new CacheBuilder(); - } - - /** - * Returns the cached value associated with the provided {@code key}. If no value is cached yet, - * computes the value using {@code mappingFunction}, stores the result, and returns it. - */ - V computeIfAbsent(K key, Function mappingFunction); - - /** - * Returns the cached value associated with the provided {@code key} if present, or {@code null} - * otherwise. - */ - @Nullable - V get(K key); - - /** Puts the {@code value} into the cache for the {@code key}. */ - void put(K key, V value); - - /** Removes a value for {@code key} if present. */ - void remove(K key); -} diff --git a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/CacheBuilder.java b/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/CacheBuilder.java deleted file mode 100644 index 08187e91a19f..000000000000 --- a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/CacheBuilder.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.caching; - -import java.util.concurrent.Executor; -import javax.annotation.Nullable; - -/** A builder of {@link Cache}. */ -public final class CacheBuilder { - - private static final long UNSET = -1; - private static final boolean USE_CAFFEINE_3 = Caffeine3Cache.available(); - - private boolean weakKeys; - private boolean weakValues; - private long maximumSize = UNSET; - @Nullable private Executor executor = null; - - /** Sets the maximum size of the cache. */ - public CacheBuilder setMaximumSize(long maximumSize) { - this.maximumSize = maximumSize; - return this; - } - - /** - * Sets that keys should be referenced weakly. If used, keys will use identity comparison, not - * {@link Object#equals(Object)}. - */ - public CacheBuilder setWeakKeys() { - this.weakKeys = true; - return this; - } - - /** Sets that values should be referenced weakly. */ - public CacheBuilder setWeakValues() { - this.weakValues = true; - return this; - } - - // Visible for testing - CacheBuilder setExecutor(Executor executor) { - this.executor = executor; - return this; - } - - /** Returns a new {@link Cache} with the settings of this {@link CacheBuilder}. */ - public Cache build() { - if (weakKeys && !weakValues && maximumSize == UNSET) { - return new WeakLockFreeCache<>(); - } - CaffeineCache.Builder caffeine = - USE_CAFFEINE_3 ? new Caffeine3Cache.Builder<>() : new Caffeine2Cache.Builder<>(); - if (weakKeys) { - caffeine.weakKeys(); - } - if (weakValues) { - caffeine.weakValues(); - } - if (maximumSize != UNSET) { - caffeine.maximumSize(maximumSize); - } - if (executor != null) { - caffeine.executor(executor); - } else { - caffeine.executor(Runnable::run); - } - return caffeine.build(); - } - - CacheBuilder() {} -} diff --git a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Caffeine2Cache.java b/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Caffeine2Cache.java deleted file mode 100644 index 2a202b3d0afe..000000000000 --- a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Caffeine2Cache.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.caching; - -import io.opentelemetry.instrumentation.api.internal.shaded.caffeine2.cache.Caffeine; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.function.Function; -import javax.annotation.Nonnegative; -import javax.annotation.Nonnull; - -final class Caffeine2Cache implements CaffeineCache { - - private final io.opentelemetry.instrumentation.api.internal.shaded.caffeine2.cache.Cache - delegate; - - Caffeine2Cache( - io.opentelemetry.instrumentation.api.internal.shaded.caffeine2.cache.Cache delegate) { - this.delegate = delegate; - } - - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - return delegate.get(key, mappingFunction); - } - - @Override - public V get(K key) { - return delegate.getIfPresent(key); - } - - @Override - public void put(K key, V value) { - delegate.put(key, value); - } - - @Override - public void remove(K key) { - delegate.invalidate(key); - } - - // Visible for testing - @Override - public Set keySet() { - return delegate.asMap().keySet(); - } - - // Visible for testing - @Override - public void cleanup() { - delegate.cleanUp(); - } - - static class Builder implements CaffeineCache.Builder { - private final Caffeine caffeine = Caffeine.newBuilder(); - - @Override - public void weakKeys() { - caffeine.weakKeys(); - } - - @Override - public void weakValues() { - caffeine.weakValues(); - } - - @Override - public void maximumSize(@Nonnegative long maximumSize) { - caffeine.maximumSize(maximumSize); - } - - @Override - public void executor(@Nonnull Executor executor) { - caffeine.executor(executor); - } - - @Override - public Cache build() { - @SuppressWarnings("unchecked") - io.opentelemetry.instrumentation.api.internal.shaded.caffeine2.cache.Cache delegate = - (io.opentelemetry.instrumentation.api.internal.shaded.caffeine2.cache.Cache) - caffeine.build(); - return new Caffeine2Cache<>(delegate); - } - } -} diff --git a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Caffeine3Cache.java b/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Caffeine3Cache.java deleted file mode 100644 index c7abfb2fd961..000000000000 --- a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/Caffeine3Cache.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.caching; - -import io.opentelemetry.instrumentation.api.internal.shaded.caffeine3.cache.Caffeine; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.function.Function; -import javax.annotation.Nonnegative; -import javax.annotation.Nonnull; - -final class Caffeine3Cache implements CaffeineCache { - - private final io.opentelemetry.instrumentation.api.internal.shaded.caffeine3.cache.Cache - delegate; - - Caffeine3Cache( - io.opentelemetry.instrumentation.api.internal.shaded.caffeine3.cache.Cache delegate) { - this.delegate = delegate; - } - - public static boolean available() { - try { - Caffeine.class.getName(); - return true; - } catch (UnsupportedClassVersionError | NoClassDefFoundError exception) { - // caffeine 3 requires jdk 11 - return false; - } - } - - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - return delegate.get(key, mappingFunction); - } - - @Override - public V get(K key) { - return delegate.getIfPresent(key); - } - - @Override - public void put(K key, V value) { - delegate.put(key, value); - } - - @Override - public void remove(K key) { - delegate.invalidate(key); - } - - // Visible for testing - @Override - public Set keySet() { - return delegate.asMap().keySet(); - } - - // Visible for testing - @Override - public void cleanup() { - delegate.cleanUp(); - } - - static class Builder implements CaffeineCache.Builder { - private final Caffeine caffeine = Caffeine.newBuilder(); - - @Override - public void weakKeys() { - caffeine.weakKeys(); - } - - @Override - public void weakValues() { - caffeine.weakValues(); - } - - @Override - public void maximumSize(@Nonnegative long maximumSize) { - caffeine.maximumSize(maximumSize); - } - - @Override - public void executor(@Nonnull Executor executor) { - caffeine.executor(executor); - } - - @Override - public Cache build() { - @SuppressWarnings("unchecked") - io.opentelemetry.instrumentation.api.internal.shaded.caffeine3.cache.Cache delegate = - (io.opentelemetry.instrumentation.api.internal.shaded.caffeine3.cache.Cache) - caffeine.build(); - return new Caffeine3Cache<>(delegate); - } - } -} diff --git a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/CaffeineCache.java b/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/CaffeineCache.java deleted file mode 100644 index 16774d07b6ae..000000000000 --- a/instrumentation-api-caching/src/main/java/io/opentelemetry/instrumentation/api/caching/CaffeineCache.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.caching; - -import java.util.Set; -import java.util.concurrent.Executor; -import javax.annotation.Nonnegative; -import javax.annotation.Nonnull; - -interface CaffeineCache extends Cache { - - interface Builder { - - void weakKeys(); - - void weakValues(); - - void maximumSize(@Nonnegative long maximumSize); - - void executor(@Nonnull Executor executor); - - Cache build(); - } - - // Visible for testing - Set keySet(); - - // Visible for testing - void cleanup(); -} diff --git a/instrumentation-api-caching/src/test/README.md b/instrumentation-api-caching/src/test/README.md deleted file mode 100644 index 865db385e21b..000000000000 --- a/instrumentation-api-caching/src/test/README.md +++ /dev/null @@ -1 +0,0 @@ -Tests for this module are in the instrumentation-api project to verify against the shaded artifact. From 37569ca53479d9801ab12cba5650d9ed925c7f3f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 16:36:32 -0800 Subject: [PATCH 21/30] Copy in weak-lock-free --- .../otel.errorprone-conventions.gradle.kts | 2 +- dependencyManagement/build.gradle.kts | 1 - instrumentation-api/build.gradle.kts | 1 - .../api/cache/WeakLockFreeCache.java | 2 +- .../AbstractWeakConcurrentMap.java | 362 ++++++++++++++++++ .../weaklockfree/DetachedThreadLocal.java | 141 +++++++ .../cache/weaklockfree/WeakConcurrentMap.java | 208 ++++++++++ .../cache/weaklockfree/WeakConcurrentSet.java | 135 +++++++ javaagent/build.gradle.kts | 3 +- 9 files changed, 850 insertions(+), 5 deletions(-) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/AbstractWeakConcurrentMap.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/DetachedThreadLocal.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentMap.java create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentSet.java diff --git a/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts b/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts index 08b5a4031df3..c6b6fa4a8371 100644 --- a/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts +++ b/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts @@ -19,7 +19,7 @@ tasks { disableWarningsInGeneratedCode.set(true) allDisabledChecksAsWarnings.set(true) - excludedPaths.set(".*/build/generated/.*|.*/concurrentlinkedhashmap/.*",) + excludedPaths.set(".*/build/generated/.*|.*/concurrentlinkedhashmap/.*|.*/weaklockfree/.*") if (System.getenv("CI") == null) { disable("SystemOut") diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 140da3d100bb..a5be877faa62 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -87,7 +87,6 @@ val DEPENDENCY_SETS = listOf( val DEPENDENCIES = listOf( "ch.qos.logback:logback-classic:1.2.3", - "com.blogspot.mydailyjava:weak-lock-free:0.18", "com.github.stefanbirkner:system-lambda:1.2.0", "com.github.stefanbirkner:system-rules:1.19.0", "com.google.auto.service:auto-service:1.0", diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts index 77d01ca5139a..400ff3c39008 100644 --- a/instrumentation-api/build.gradle.kts +++ b/instrumentation-api/build.gradle.kts @@ -25,7 +25,6 @@ dependencies { implementation("io.opentelemetry:opentelemetry-api-metrics") implementation("org.slf4j:slf4j-api") - implementation("com.blogspot.mydailyjava:weak-lock-free") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/WeakLockFreeCache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/WeakLockFreeCache.java index 9576bab295e3..0c07e323971a 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/WeakLockFreeCache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/WeakLockFreeCache.java @@ -5,7 +5,7 @@ package io.opentelemetry.instrumentation.api.cache; -import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap; +import io.opentelemetry.instrumentation.api.cache.weaklockfree.WeakConcurrentMap; import java.util.function.Function; final class WeakLockFreeCache implements Cache { diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/AbstractWeakConcurrentMap.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/AbstractWeakConcurrentMap.java new file mode 100644 index 000000000000..0e451d439f37 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/AbstractWeakConcurrentMap.java @@ -0,0 +1,362 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.weaklockfree; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A thread-safe map with weak keys. Entries are based on a key's system hash code and keys are + * considered equal only by reference equality. This class offers an abstract-base implementation + * that allows to override methods. This class does not implement the {@link Map} interface because + * this implementation is incompatible with the map contract. While iterating over a map's entries, + * any key that has not passed iteration is referenced non-weakly. + */ +public abstract class AbstractWeakConcurrentMap extends ReferenceQueue + implements Runnable, Iterable> { + + final ConcurrentMap, V> target; + + protected AbstractWeakConcurrentMap() { + this(new ConcurrentHashMap<>()); + } + + /** @param target ConcurrentMap implementation that this class wraps. */ + protected AbstractWeakConcurrentMap(ConcurrentMap, V> target) { + this.target = target; + } + + /** + * Override with care as it can cause lookup failures if done incorrectly. The result must have + * the same {@link Object#hashCode()} as the input and be {@link Object#equals(Object) equal to} a + * weak reference of the key. When overriding this, also override {@link #resetLookupKey}. + */ + protected abstract L getLookupKey(K key); + + /** Resets any reusable state in the {@linkplain #getLookupKey lookup key}. */ + protected abstract void resetLookupKey(L lookupKey); + + /** + * @param key The key of the entry. + * @return The value of the entry or the default value if it did not exist. + */ + public V get(K key) { + if (key == null) { + throw new NullPointerException(); + } + V value; + L lookupKey = getLookupKey(key); + try { + value = target.get(lookupKey); + } finally { + resetLookupKey(lookupKey); + } + if (value == null) { + value = defaultValue(key); + if (value != null) { + V previousValue = target.putIfAbsent(new WeakKey<>(key, this), value); + if (previousValue != null) { + value = previousValue; + } + } + } + return value; + } + + /** + * @param key The key of the entry. + * @return The value of the entry or null if it did not exist. + */ + public V getIfPresent(K key) { + if (key == null) { + throw new NullPointerException(); + } + L lookupKey = getLookupKey(key); + try { + return target.get(lookupKey); + } finally { + resetLookupKey(lookupKey); + } + } + + /** + * @param key The key of the entry. + * @return {@code true} if the key already defines a value. + */ + public boolean containsKey(K key) { + if (key == null) { + throw new NullPointerException(); + } + L lookupKey = getLookupKey(key); + try { + return target.containsKey(lookupKey); + } finally { + resetLookupKey(lookupKey); + } + } + + /** + * @param key The key of the entry. + * @param value The value of the entry. + * @return The previous entry or {@code null} if it does not exist. + */ + public V put(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException(); + } + return target.put(new WeakKey<>(key, this), value); + } + + /** + * @param key The key of the entry. + * @param value The value of the entry. + * @return The previous entry or {@code null} if it does not exist. + */ + public V putIfAbsent(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException(); + } + V previous; + L lookupKey = getLookupKey(key); + try { + previous = target.get(lookupKey); + } finally { + resetLookupKey(lookupKey); + } + return previous == null ? target.putIfAbsent(new WeakKey<>(key, this), value) : previous; + } + + /** + * @param key The key of the entry. + * @param value The value of the entry. + * @return The previous entry or {@code null} if it does not exist. + */ + public V putIfProbablyAbsent(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException(); + } + return target.putIfAbsent(new WeakKey<>(key, this), value); + } + + /** + * @param key The key of the entry. + * @return The removed entry or {@code null} if it does not exist. + */ + public V remove(K key) { + if (key == null) { + throw new NullPointerException(); + } + L lookupKey = getLookupKey(key); + try { + return target.remove(lookupKey); + } finally { + resetLookupKey(lookupKey); + } + } + + /** Clears the entire map. */ + public void clear() { + target.clear(); + } + + /** + * Creates a default value. There is no guarantee that the requested value will be set as a once + * it is created in case that another thread requests a value for a key concurrently. + * + * @param key The key for which to create a default value. + * @return The default value for a key without value or {@code null} for not defining a default + * value. + */ + protected V defaultValue(K key) { + return null; + } + + /** Cleans all unused references. */ + public void expungeStaleEntries() { + Reference reference; + while ((reference = poll()) != null) { + target.remove(reference); + } + } + + /** + * Returns the approximate size of this map where the returned number is at least as big as the + * actual number of entries. + * + * @return The minimum size of this map. + */ + public int approximateSize() { + return target.size(); + } + + @Override + public void run() { + try { + while (!Thread.interrupted()) { + target.remove(remove()); + } + } catch (InterruptedException ignored) { + // do nothing + } + } + + @Override + public Iterator> iterator() { + return new EntryIterator(target.entrySet().iterator()); + } + + @Override + public String toString() { + return target.toString(); + } + + /* + * Why this works: + * --------------- + * + * Note that this map only supports reference equality for keys and uses system hash codes. Also, for the + * WeakKey instances to function correctly, we are voluntarily breaking the Java API contract for + * hashCode/equals of these instances. + * + * System hash codes are immutable and can therefore be computed prematurely and are stored explicitly + * within the WeakKey instances. This way, we always know the correct hash code of a key and always + * end up in the correct bucket of our target map. This remains true even after the weakly referenced + * key is collected. + * + * If we are looking up the value of the current key via WeakConcurrentMap::get or any other public + * API method, we know that any value associated with this key must still be in the map as the mere + * existence of this key makes it ineligible for garbage collection. Therefore, looking up a value + * using another WeakKey wrapper guarantees a correct result. + * + * If we are looking up the map entry of a WeakKey after polling it from the reference queue, we know + * that the actual key was already collected and calling WeakKey::get returns null for both the polled + * instance and the instance within the map. Since we explicitly stored the identity hash code for the + * referenced value, it is however trivial to identify the correct bucket. From this bucket, the first + * weak key with a null reference is removed. Due to hash collision, we do not know if this entry + * represents the weak key. However, we do know that the reference queue polls at least as many weak + * keys as there are stale map entries within the target map. If no key is ever removed from the map + * explicitly, the reference queue eventually polls exactly as many weak keys as there are stale entries. + * + * Therefore, we can guarantee that there is no memory leak. + * + * It is the responsibility of the actual map implementation to implement a lookup key that is used for + * lookups. The lookup key must supply the same semantics as the weak key with regards to hash code. + * The weak key invokes the latent key's equality method upon evaluation. + */ + + public static final class WeakKey extends WeakReference { + + private final int hashCode; + + WeakKey(K key, ReferenceQueue queue) { + super(key, queue); + hashCode = System.identityHashCode(key); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object other) { + if (other instanceof WeakKey) { + return ((WeakKey) other).get() == get(); + } else { + return other.equals(this); + } + } + + @Override + public String toString() { + return String.valueOf(get()); + } + } + + private class EntryIterator implements Iterator> { + + private final Iterator, V>> iterator; + + private Map.Entry, V> nextEntry; + + private K nextKey; + + private EntryIterator(Iterator, V>> iterator) { + this.iterator = iterator; + findNext(); + } + + private void findNext() { + while (iterator.hasNext()) { + nextEntry = iterator.next(); + nextKey = nextEntry.getKey().get(); + if (nextKey != null) { + return; + } + } + nextEntry = null; + nextKey = null; + } + + @Override + public boolean hasNext() { + return nextKey != null; + } + + @Override + public Map.Entry next() { + if (nextKey == null) { + throw new NoSuchElementException(); + } + try { + return new SimpleEntry(nextKey, nextEntry); + } finally { + findNext(); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private class SimpleEntry implements Map.Entry { + + private final K key; + + final Map.Entry, V> entry; + + private SimpleEntry(K key, Map.Entry, V> entry) { + this.key = key; + this.entry = entry; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return entry.getValue(); + } + + @Override + public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } + return entry.setValue(value); + } + } +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/DetachedThreadLocal.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/DetachedThreadLocal.java new file mode 100644 index 000000000000..5f874b6cc8c7 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/DetachedThreadLocal.java @@ -0,0 +1,141 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.weaklockfree; + +/** + * A detached local that allows for explicit control of setting and removing values from a + * thread-local context. Instances of this class are non-blocking and fully thread safe. + */ +public class DetachedThreadLocal implements Runnable { + + final WeakConcurrentMap map; + + public DetachedThreadLocal(Cleaner cleaner) { + switch (cleaner) { + case THREAD: + case MANUAL: + map = + new WeakConcurrentMap(cleaner == Cleaner.THREAD) { + @Override + protected T defaultValue(Thread key) { + return DetachedThreadLocal.this.initialValue(key); + } + }; + break; + case INLINE: + map = + new WeakConcurrentMap.WithInlinedExpunction() { + @Override + protected T defaultValue(Thread key) { + return DetachedThreadLocal.this.initialValue(key); + } + }; + break; + default: + throw new AssertionError(); + } + } + + public T get() { + return map.get(Thread.currentThread()); + } + + public T getIfPresent() { + return map.getIfPresent(Thread.currentThread()); + } + + public void set(T value) { + map.put(Thread.currentThread(), value); + } + + public void clear() { + map.remove(Thread.currentThread()); + } + + /** Clears all thread local references for all threads. */ + public void clearAll() { + map.clear(); + } + + /** + * @param thread The thread to which this thread's thread local value should be pushed. + * @return The value being set. + */ + public T pushTo(Thread thread) { + T value = get(); + if (value != null) { + map.put(thread, inheritValue(value)); + } + return value; + } + + /** + * @param thread The thread from which the thread thread local value should be fetched. + * @return The value being set. + */ + public T fetchFrom(Thread thread) { + T value = map.get(thread); + if (value != null) { + set(inheritValue(value)); + } + return value; + } + + /** + * @param thread The thread for which to set a thread-local value. + * @return The value accociated with this thread. + */ + public T get(Thread thread) { + return map.get(thread); + } + + /** + * @param thread The thread for which to set a thread-local value. + * @param value The value to set. + */ + public void define(Thread thread, T value) { + map.put(thread, value); + } + + /** + * @param thread The thread for which an initial value is created. + * @return The initial value for any thread local. If no default is set, the default value is + * {@code null}. + */ + protected T initialValue(Thread thread) { + return null; + } + + /** + * @param value The value that is inherited. + * @return The inherited value. + */ + protected T inheritValue(T value) { + return value; + } + + /** @return The weak map that backs this detached thread local. */ + public WeakConcurrentMap getBackingMap() { + return map; + } + + @Override + public void run() { + map.run(); + } + + /** + * Determines the cleaning format. A reference is removed either by an explicitly started cleaner + * thread associated with this instance ({@link Cleaner#THREAD}), as a result of interacting with + * this thread local from any thread ({@link Cleaner#INLINE} or manually by submitting the + * detached thread local to a thread ({@link Cleaner#MANUAL}). + */ + public enum Cleaner { + THREAD, + INLINE, + MANUAL + } +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentMap.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentMap.java new file mode 100644 index 000000000000..08499cc0257a --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentMap.java @@ -0,0 +1,208 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.weaklockfree; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A thread-safe map with weak keys. Entries are based on a key's system hash code and keys are + * considered equal only by reference equality. This class does not implement the {@link + * java.util.Map} interface because this implementation is incompatible with the map contract. While + * iterating over a map's entries, any key that has not passed iteration is referenced non-weakly. + */ +public class WeakConcurrentMap + extends AbstractWeakConcurrentMap> { + + /** + * Lookup keys are cached thread-locally to avoid allocations on lookups. This is beneficial as + * the JIT unfortunately can't reliably replace the {@link LookupKey} allocation with stack + * allocations, even though the {@link LookupKey} does not escape. + */ + private static final ThreadLocal> LOOKUP_KEY_CACHE = + new ThreadLocal>() { + @Override + protected LookupKey initialValue() { + return new LookupKey<>(); + } + }; + + private static final AtomicLong ID = new AtomicLong(); + + private final Thread thread; + + private final boolean reuseKeys; + + /** @param cleanerThread {@code true} if a thread should be started that removes stale entries. */ + public WeakConcurrentMap(boolean cleanerThread) { + this(cleanerThread, isPersistentClassLoader(LookupKey.class.getClassLoader())); + } + + /** + * Checks whether the provided {@link ClassLoader} may be unloaded like a web application class + * loader, for example. + * + *

    If the class loader can't be unloaded, it is safe to use {@link ThreadLocal}s and to reuse + * the {@link LookupKey}. Otherwise, the use of {@link ThreadLocal}s may lead to class loader + * leaks as it prevents the class loader this class is loaded by to unload. + * + * @param classLoader The class loader to check. + * @return {@code true} if the provided class loader can be unloaded. + */ + private static boolean isPersistentClassLoader(ClassLoader classLoader) { + try { + return classLoader == null // bootstrap class loader + || classLoader == ClassLoader.getSystemClassLoader() + || classLoader + == ClassLoader.getSystemClassLoader().getParent(); // ext/platfrom class loader; + } catch (Throwable ignored) { + return false; + } + } + + /** + * @param cleanerThread {@code true} if a thread should be started that removes stale entries. + * @param reuseKeys {@code true} if the lookup keys should be reused via a {@link ThreadLocal}. + * Note that setting this to {@code true} may result in class loader leaks. See {@link + * #isPersistentClassLoader(ClassLoader)} for more details. + */ + public WeakConcurrentMap(boolean cleanerThread, boolean reuseKeys) { + this(cleanerThread, reuseKeys, new ConcurrentHashMap<>()); + } + + /** + * @param cleanerThread {@code true} if a thread should be started that removes stale entries. + * @param reuseKeys {@code true} if the lookup keys should be reused via a {@link ThreadLocal}. + * Note that setting this to {@code true} may result in class loader leaks. See {@link + * #isPersistentClassLoader(ClassLoader)} for more details. + * @param target ConcurrentMap implementation that this class wraps. + */ + public WeakConcurrentMap( + boolean cleanerThread, boolean reuseKeys, ConcurrentMap, V> target) { + super(target); + this.reuseKeys = reuseKeys; + if (cleanerThread) { + thread = new Thread(this); + thread.setName("weak-ref-cleaner-" + ID.getAndIncrement()); + thread.setPriority(Thread.MIN_PRIORITY); + thread.setDaemon(true); + thread.start(); + } else { + thread = null; + } + } + + @Override + @SuppressWarnings("unchecked") + protected LookupKey getLookupKey(K key) { + LookupKey lookupKey; + if (reuseKeys) { + lookupKey = (LookupKey) LOOKUP_KEY_CACHE.get(); + } else { + lookupKey = new LookupKey<>(); + } + return lookupKey.withValue(key); + } + + @Override + protected void resetLookupKey(LookupKey lookupKey) { + lookupKey.reset(); + } + + /** @return The cleaner thread or {@code null} if no such thread was set. */ + public Thread getCleanerThread() { + return thread; + } + + /* + * A lookup key must only be used for looking up instances within a map. For this to work, it implements an identical contract for + * hash code and equals as the WeakKey implementation. At the same time, the lookup key implementation does not extend WeakReference + * and avoids the overhead that a weak reference implies. + */ + + // can't use AutoClosable/try-with-resources as this project still supports Java 6 + static final class LookupKey { + + private K key; + private int hashCode; + + LookupKey withValue(K key) { + this.key = key; + hashCode = System.identityHashCode(key); + return this; + } + + /** Failing to reset a lookup key can lead to memory leaks as the key is strongly referenced. */ + void reset() { + key = null; + hashCode = 0; + } + + @Override + public boolean equals(Object other) { + if (other instanceof WeakConcurrentMap.LookupKey) { + return ((LookupKey) other).key == key; + } else { + return ((WeakKey) other).get() == key; + } + } + + @Override + public int hashCode() { + return hashCode; + } + } + + /** + * A {@link WeakConcurrentMap} where stale entries are removed as a side effect of interacting + * with this map. + */ + public static class WithInlinedExpunction extends WeakConcurrentMap { + + public WithInlinedExpunction() { + super(false); + } + + @Override + public V get(K key) { + expungeStaleEntries(); + return super.get(key); + } + + @Override + public boolean containsKey(K key) { + expungeStaleEntries(); + return super.containsKey(key); + } + + @Override + public V put(K key, V value) { + expungeStaleEntries(); + return super.put(key, value); + } + + @Override + public V remove(K key) { + expungeStaleEntries(); + return super.remove(key); + } + + @Override + public Iterator> iterator() { + expungeStaleEntries(); + return super.iterator(); + } + + @Override + public int approximateSize() { + expungeStaleEntries(); + return super.approximateSize(); + } + } +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentSet.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentSet.java new file mode 100644 index 000000000000..98cf9b93ef48 --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentSet.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.cache.weaklockfree; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +/** + * A thread-safe set with weak values. Entries are based on a key's system hash code and keys are + * considered equal only by reference equality. This class does not implement the {@link + * java.util.Set} interface because this implementation is incompatible with the set contract. While + * iterating over a set's entries, any value that has not passed iteration is referenced non-weakly. + */ +public class WeakConcurrentSet implements Runnable, Iterable { + + final WeakConcurrentMap target; + + public WeakConcurrentSet(Cleaner cleaner) { + switch (cleaner) { + case INLINE: + target = new WeakConcurrentMap.WithInlinedExpunction<>(); + break; + case THREAD: + case MANUAL: + target = new WeakConcurrentMap<>(cleaner == Cleaner.THREAD); + break; + default: + throw new AssertionError(); + } + } + + /** + * @param value The value to add to the set. + * @return {@code true} if the value was added to the set and was not contained before. + */ + public boolean add(V value) { + return target.put(value, Boolean.TRUE) == null; // is null or Boolean.TRUE + } + + /** + * @param value The value to check if it is contained in the set. + * @return {@code true} if the set contains the value. + */ + public boolean contains(V value) { + return target.containsKey(value); + } + + /** + * @param value The value to remove from the set. + * @return {@code true} if the value is contained in the set. + */ + public boolean remove(V value) { + return target.remove(value) != null; // is null or Boolean.TRUE + } + + /** Clears the set. */ + public void clear() { + target.clear(); + } + + /** + * Returns the approximate size of this set where the returned number is at least as big as the + * actual number of entries. + * + * @return The minimum size of this set. + */ + public int approximateSize() { + return target.approximateSize(); + } + + @Override + public void run() { + target.run(); + } + + /** + * Determines the cleaning format. A reference is removed either by an explicitly started cleaner + * thread associated with this instance ({@link Cleaner#THREAD}), as a result of interacting with + * this thread local from any thread ({@link Cleaner#INLINE} or manually by submitting the + * detached thread local to a thread ({@link Cleaner#MANUAL}). + */ + public enum Cleaner { + THREAD, + INLINE, + MANUAL + } + + /** Cleans all unused references. */ + public void expungeStaleEntries() { + target.expungeStaleEntries(); + } + + /** @return The cleaner thread or {@code null} if no such thread was set. */ + public Thread getCleanerThread() { + return target.getCleanerThread(); + } + + @Override + public Iterator iterator() { + return new ReducingIterator<>(target.iterator()); + } + + @Override + public String toString() { + return Collections.newSetFromMap(target.target).toString(); + } + + private static class ReducingIterator implements Iterator { + + private final Iterator> iterator; + + private ReducingIterator(Iterator> iterator) { + this.iterator = iterator; + } + + @Override + public void remove() { + iterator.remove(); + } + + @Override + public V next() { + return iterator.next().getKey(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + } +} diff --git a/javaagent/build.gradle.kts b/javaagent/build.gradle.kts index 3fb74e2dfb38..cce8977bed55 100644 --- a/javaagent/build.gradle.kts +++ b/javaagent/build.gradle.kts @@ -87,7 +87,8 @@ dependencies { licenseReportDependencies("com.github.ben-manes.caffeine:caffeine:$caffeine3Version") { isTransitive = false } - licenseReportDependencies("com.blogspot.mydailyjava:weak-lock-free") + licenseReportDependencies("com.blogspot.mydailyjava:weak-lock-free:0.18") + licenseReportDependencies("com.blogspot.mydailyjava:weak-lock-free:0.18") // TODO ideally this would be :instrumentation instead of :javaagent-tooling // in case there are dependencies (accidentally) pulled in by instrumentation modules // but I couldn't get that to work From fe8ff0bf48bdefa0072632897388e7d55d1453cb Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 16:49:10 -0800 Subject: [PATCH 22/30] Remove caffeine remnants --- benchmark/build.gradle.kts | 2 +- ...try.instrumentation.javaagent-shadowing.gradle.kts | 1 - dependencyManagement/build.gradle.kts | 3 --- .../javaagent/tooling/AgentInstaller.java | 11 ----------- javaagent/build.gradle.kts | 9 ++------- .../AgentInstrumentationSpecificationTest.groovy | 5 ----- 6 files changed, 3 insertions(+), 28 deletions(-) diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index 45238f05d349..37333d1bcd60 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { jmh(project(":javaagent-tooling")) jmh(project(":javaagent-extension-api")) - jmh("com.github.ben-manes.caffeine:caffeine:$caffeine2Version") + jmh("com.github.ben-manes.caffeine:caffeine:2.9.2") jmh("javax.servlet:javax.servlet-api:4.0.1") jmh("com.google.http-client:google-http-client:1.19.0") diff --git a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-shadowing.gradle.kts b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-shadowing.gradle.kts index 06b9673a0627..80935e069067 100644 --- a/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-shadowing.gradle.kts +++ b/conventions/src/main/kotlin/io.opentelemetry.instrumentation.javaagent-shadowing.gradle.kts @@ -8,7 +8,6 @@ tasks.withType().configureEach { mergeServiceFiles() exclude("**/module-info.class") - exclude("META-INF/jandex.idx") // from caffeine // Prevents conflict with other SLF4J instances. Important for premain. relocate("org.slf4j", "io.opentelemetry.javaagent.slf4j") diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index a5be877faa62..df4217670925 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -17,9 +17,6 @@ rootProject.extra["otelVersion"] = otelVersion // Need both BOM and -all val groovyVersion = "2.5.14" -rootProject.extra["caffeine2Version"] = "2.9.2" -rootProject.extra["caffeine3Version"] = "3.0.4" - // We don't force libraries we instrument to new versions since we compile and test against specific // old baseline versions // but we do try to force those libraries' transitive dependencies to new versions where possible diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java index 9f9154c3265e..6a22a2472f4f 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java @@ -38,8 +38,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.annotation.Nullable; @@ -86,15 +84,6 @@ public class AgentInstaller { // internal-proxy instrumentation module after the bytebuddy transformer is set up Proxy.class.getName(); - // caffeine can trigger first access of ForkJoinPool under transform(), which leads ForkJoinPool - // not to get transformed itself. - // loading it early here still allows it to be retransformed as part of agent installation below - ForkJoinPool.class.getName(); - - // caffeine uses AtomicReferenceArray, ensure it is loaded to avoid ClassCircularityError during - // transform. - AtomicReferenceArray.class.getName(); - Integer strictContextStressorMillis = Integer.getInteger(STRICT_CONTEXT_STRESSOR_MILLIS); if (strictContextStressorMillis != null) { io.opentelemetry.context.ContextStorage.addWrapper( diff --git a/javaagent/build.gradle.kts b/javaagent/build.gradle.kts index cce8977bed55..b53d45ce75b0 100644 --- a/javaagent/build.gradle.kts +++ b/javaagent/build.gradle.kts @@ -55,8 +55,6 @@ val licenseReportDependencies by configurations.creating { extendsFrom(bootstrapLibs) } -val caffeine3Version: String by project - dependencies { bootstrapLibs(project(":instrumentation-api")) bootstrapLibs(project(":instrumentation-api-annotation-support")) @@ -83,11 +81,8 @@ dependencies { exporterSlimLibs("io.opentelemetry:opentelemetry-exporter-otlp") exporterSlimLibs("io.opentelemetry:opentelemetry-exporter-otlp-metrics") - // We only have compileOnly dependencies on these to make sure they don't leak into POMs. - licenseReportDependencies("com.github.ben-manes.caffeine:caffeine:$caffeine3Version") { - isTransitive = false - } - licenseReportDependencies("com.blogspot.mydailyjava:weak-lock-free:0.18") + // concurrentlinkedhashmap-lru and weak-lock-free are copied in to the instrumentation-api module + licenseReportDependencies("com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2") licenseReportDependencies("com.blogspot.mydailyjava:weak-lock-free:0.18") // TODO ideally this would be :instrumentation instead of :javaagent-tooling // in case there are dependencies (accidentally) pulled in by instrumentation modules diff --git a/testing-common/integration-tests/src/test/groovy/AgentInstrumentationSpecificationTest.groovy b/testing-common/integration-tests/src/test/groovy/AgentInstrumentationSpecificationTest.groovy index 7a7262ea8bd2..5692e7daa4fb 100644 --- a/testing-common/integration-tests/src/test/groovy/AgentInstrumentationSpecificationTest.groovy +++ b/testing-common/integration-tests/src/test/groovy/AgentInstrumentationSpecificationTest.groovy @@ -10,7 +10,6 @@ import io.opentelemetry.javaagent.tooling.Constants import org.slf4j.LoggerFactory import java.util.concurrent.TimeoutException -import spock.util.environment.Jvm // this test is run using // -Dotel.javaagent.exclude-classes=config.exclude.packagename.*,config.exclude.SomeClass,config.exclude.SomeClass$NestedClass @@ -22,10 +21,6 @@ class AgentInstrumentationSpecificationTest extends AgentInstrumentationSpecific setup: final List bootstrapClassesIncorrectlyLoaded = [] for (ClassPath.ClassInfo info : getTestClasspath().getAllClasses()) { - if (info.getName().toLowerCase().contains("caffeine3") && !Jvm.getCurrent().isJava11Compatible()) { - // caffeine 3 classes are compiled for java 11 - continue - } for (int i = 0; i < Constants.BOOTSTRAP_PACKAGE_PREFIXES.size(); ++i) { if (info.getName().startsWith(Constants.BOOTSTRAP_PACKAGE_PREFIXES[i])) { Class bootstrapClass = Class.forName(info.getName()) From d4d02dac29fd30595ee2156d54fe5d5a53f3d4e8 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 18:14:08 -0800 Subject: [PATCH 23/30] Fix checkstyle --- instrumentation-api/build.gradle.kts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts index 400ff3c39008..e5209ce02f08 100644 --- a/instrumentation-api/build.gradle.kts +++ b/instrumentation-api/build.gradle.kts @@ -39,6 +39,11 @@ dependencies { } tasks { + named("checkstyleMain") { + exclude("**/concurrentlinkedhashmap/**") + exclude("**/weaklockfree/**") + } + sourcesJar { dependsOn("generateJflex") } From 55f82921e52a747b10779519e537787d29425ba0 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 19:06:43 -0800 Subject: [PATCH 24/30] Rename BoundedCache to MapBackedCached --- .../io/opentelemetry/instrumentation/api/cache/Cache.java | 2 +- .../api/cache/{BoundedCache.java => MapBackedCache.java} | 4 ++-- .../instrumentation/api/cache/CacheTest.java | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) rename instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/{BoundedCache.java => MapBackedCache.java} (88%) diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java index 32901b1e1548..699121370ecd 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java @@ -35,7 +35,7 @@ static Cache weak() { static Cache bounded(int capacity) { ConcurrentLinkedHashMap map = new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity(capacity).build(); - return new BoundedCache<>(map); + return new MapBackedCache<>(map); } /** diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/MapBackedCache.java similarity index 88% rename from instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java rename to instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/MapBackedCache.java index 2cddd962efef..4d2d47574386 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/BoundedCache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/MapBackedCache.java @@ -9,11 +9,11 @@ import java.util.function.Function; import javax.annotation.Nullable; -final class BoundedCache implements Cache { +final class MapBackedCache implements Cache { private final ConcurrentMap delegate; - BoundedCache(ConcurrentMap delegate) { + MapBackedCache(ConcurrentMap delegate) { this.delegate = delegate; } diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java index 8e10714632be..d81a7e01c907 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/cache/CacheTest.java @@ -23,16 +23,16 @@ void bounded() { assertThat(cache.computeIfAbsent("bear", unused -> "roar")).isEqualTo("roar"); cache.remove("bear"); - BoundedCache boundedCache = ((BoundedCache) cache); + MapBackedCache mapBackedCache = ((MapBackedCache) cache); assertThat(cache.computeIfAbsent("cat", unused -> "meow")).isEqualTo("meow"); - assertThat(boundedCache.size()).isEqualTo(1); + assertThat(mapBackedCache.size()).isEqualTo(1); assertThat(cache.computeIfAbsent("cat", unused -> "bark")).isEqualTo("meow"); - assertThat(boundedCache.size()).isEqualTo(1); + assertThat(mapBackedCache.size()).isEqualTo(1); cache.put("dog", "bark"); assertThat(cache.get("dog")).isEqualTo("bark"); - assertThat(boundedCache.size()).isEqualTo(1); + assertThat(mapBackedCache.size()).isEqualTo(1); assertThat(cache.computeIfAbsent("cat", unused -> "purr")).isEqualTo("purr"); } } From ce93d404dc87980cdd476617bc98bc701f989ff5 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 19:07:40 -0800 Subject: [PATCH 25/30] Remove duplicate LICENSE --- .../api/cache/concurrentlinkedhashmap/LICENSE | 201 ------------------ 1 file changed, 201 deletions(-) delete mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LICENSE diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LICENSE b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LICENSE deleted file mode 100644 index 261eeb9e9f8b..000000000000 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. From 03824a42e8d1854fd10a5d194992af37a2f19b37 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 19:07:51 -0800 Subject: [PATCH 26/30] Remove outdated comment --- .../io/opentelemetry/instrumentation/api/cache/Cache.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java index 699121370ecd..d050acca0b9a 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/Cache.java @@ -9,12 +9,7 @@ import java.util.function.Function; import javax.annotation.Nullable; -/** - * A cache from keys to values. - * - *

    Keys are always referenced weakly and are compared using identity comparison, not {@link - * Object#equals(Object)}. - */ +/** A cache from keys to values. */ public interface Cache { /** From a8805fbfe5bde8714f603943a03e055e24ab68b1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 19:13:07 -0800 Subject: [PATCH 27/30] Sync with SDK copy of weaklockfree --- .../otel.errorprone-conventions.gradle.kts | 2 +- instrumentation-api/build.gradle.kts | 1 - .../AbstractWeakConcurrentMap.java | 27 +++- .../weaklockfree/DetachedThreadLocal.java | 141 ------------------ .../cache/weaklockfree/WeakConcurrentMap.java | 31 ++++ .../cache/weaklockfree/WeakConcurrentSet.java | 135 ----------------- 6 files changed, 58 insertions(+), 279 deletions(-) delete mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/DetachedThreadLocal.java delete mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentSet.java diff --git a/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts b/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts index c6b6fa4a8371..d8dd3a6ae7b6 100644 --- a/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts +++ b/conventions/src/main/kotlin/otel.errorprone-conventions.gradle.kts @@ -19,7 +19,7 @@ tasks { disableWarningsInGeneratedCode.set(true) allDisabledChecksAsWarnings.set(true) - excludedPaths.set(".*/build/generated/.*|.*/concurrentlinkedhashmap/.*|.*/weaklockfree/.*") + excludedPaths.set(".*/build/generated/.*|.*/concurrentlinkedhashmap/.*") if (System.getenv("CI") == null) { disable("SystemOut") diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts index e5209ce02f08..08123c57de29 100644 --- a/instrumentation-api/build.gradle.kts +++ b/instrumentation-api/build.gradle.kts @@ -41,7 +41,6 @@ dependencies { tasks { named("checkstyleMain") { exclude("**/concurrentlinkedhashmap/**") - exclude("**/weaklockfree/**") } sourcesJar { diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/AbstractWeakConcurrentMap.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/AbstractWeakConcurrentMap.java index 0e451d439f37..ae92847ebd6a 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/AbstractWeakConcurrentMap.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/AbstractWeakConcurrentMap.java @@ -3,6 +3,26 @@ * SPDX-License-Identifier: Apache-2.0 */ +// Includes work from: +/* + * Copyright Rafael Winterhalter + * + * 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. + */ + +// Suppress warnings since this is vendored as-is. +// CHECKSTYLE:OFF + package io.opentelemetry.instrumentation.api.cache.weaklockfree; import java.lang.ref.Reference; @@ -20,8 +40,13 @@ * that allows to override methods. This class does not implement the {@link Map} interface because * this implementation is incompatible with the map contract. While iterating over a map's entries, * any key that has not passed iteration is referenced non-weakly. + * + *

    This class has been copied as is from + * https://github.com/raphw/weak-lock-free/blob/ad0e5e0c04d4a31f9485bf12b89afbc9d75473b3/src/main/java/com/blogspot/mydailyjava/weaklockfree/WeakConcurrentMap.java */ -public abstract class AbstractWeakConcurrentMap extends ReferenceQueue +// Suppress warnings since this is vendored as-is. +@SuppressWarnings({"MissingSummary", "EqualsBrokenForNull", "FieldMissingNullable"}) +abstract class AbstractWeakConcurrentMap extends ReferenceQueue implements Runnable, Iterable> { final ConcurrentMap, V> target; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/DetachedThreadLocal.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/DetachedThreadLocal.java deleted file mode 100644 index 5f874b6cc8c7..000000000000 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/DetachedThreadLocal.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.cache.weaklockfree; - -/** - * A detached local that allows for explicit control of setting and removing values from a - * thread-local context. Instances of this class are non-blocking and fully thread safe. - */ -public class DetachedThreadLocal implements Runnable { - - final WeakConcurrentMap map; - - public DetachedThreadLocal(Cleaner cleaner) { - switch (cleaner) { - case THREAD: - case MANUAL: - map = - new WeakConcurrentMap(cleaner == Cleaner.THREAD) { - @Override - protected T defaultValue(Thread key) { - return DetachedThreadLocal.this.initialValue(key); - } - }; - break; - case INLINE: - map = - new WeakConcurrentMap.WithInlinedExpunction() { - @Override - protected T defaultValue(Thread key) { - return DetachedThreadLocal.this.initialValue(key); - } - }; - break; - default: - throw new AssertionError(); - } - } - - public T get() { - return map.get(Thread.currentThread()); - } - - public T getIfPresent() { - return map.getIfPresent(Thread.currentThread()); - } - - public void set(T value) { - map.put(Thread.currentThread(), value); - } - - public void clear() { - map.remove(Thread.currentThread()); - } - - /** Clears all thread local references for all threads. */ - public void clearAll() { - map.clear(); - } - - /** - * @param thread The thread to which this thread's thread local value should be pushed. - * @return The value being set. - */ - public T pushTo(Thread thread) { - T value = get(); - if (value != null) { - map.put(thread, inheritValue(value)); - } - return value; - } - - /** - * @param thread The thread from which the thread thread local value should be fetched. - * @return The value being set. - */ - public T fetchFrom(Thread thread) { - T value = map.get(thread); - if (value != null) { - set(inheritValue(value)); - } - return value; - } - - /** - * @param thread The thread for which to set a thread-local value. - * @return The value accociated with this thread. - */ - public T get(Thread thread) { - return map.get(thread); - } - - /** - * @param thread The thread for which to set a thread-local value. - * @param value The value to set. - */ - public void define(Thread thread, T value) { - map.put(thread, value); - } - - /** - * @param thread The thread for which an initial value is created. - * @return The initial value for any thread local. If no default is set, the default value is - * {@code null}. - */ - protected T initialValue(Thread thread) { - return null; - } - - /** - * @param value The value that is inherited. - * @return The inherited value. - */ - protected T inheritValue(T value) { - return value; - } - - /** @return The weak map that backs this detached thread local. */ - public WeakConcurrentMap getBackingMap() { - return map; - } - - @Override - public void run() { - map.run(); - } - - /** - * Determines the cleaning format. A reference is removed either by an explicitly started cleaner - * thread associated with this instance ({@link Cleaner#THREAD}), as a result of interacting with - * this thread local from any thread ({@link Cleaner#INLINE} or manually by submitting the - * detached thread local to a thread ({@link Cleaner#MANUAL}). - */ - public enum Cleaner { - THREAD, - INLINE, - MANUAL - } -} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentMap.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentMap.java index 08499cc0257a..3aee7ea186bc 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentMap.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentMap.java @@ -3,6 +3,26 @@ * SPDX-License-Identifier: Apache-2.0 */ +// Includes work from: +/* + * Copyright Rafael Winterhalter + * + * 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. + */ + +// Suppress warnings since this is vendored as-is. +// CHECKSTYLE:OFF + package io.opentelemetry.instrumentation.api.cache.weaklockfree; import java.util.Iterator; @@ -16,7 +36,18 @@ * considered equal only by reference equality. This class does not implement the {@link * java.util.Map} interface because this implementation is incompatible with the map contract. While * iterating over a map's entries, any key that has not passed iteration is referenced non-weakly. + * + *

    This class has been copied as is from + * https://github.com/raphw/weak-lock-free/blob/ad0e5e0c04d4a31f9485bf12b89afbc9d75473b3/src/main/java/com/blogspot/mydailyjava/weaklockfree/WeakConcurrentMap.java */ +// Suppress warnings since this is copied as-is. +@SuppressWarnings({ + "HashCodeToString", + "MissingSummary", + "UngroupedOverloads", + "ThreadPriorityCheck", + "FieldMissingNullable" +}) public class WeakConcurrentMap extends AbstractWeakConcurrentMap> { diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentSet.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentSet.java deleted file mode 100644 index 98cf9b93ef48..000000000000 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/weaklockfree/WeakConcurrentSet.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.api.cache.weaklockfree; - -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; - -/** - * A thread-safe set with weak values. Entries are based on a key's system hash code and keys are - * considered equal only by reference equality. This class does not implement the {@link - * java.util.Set} interface because this implementation is incompatible with the set contract. While - * iterating over a set's entries, any value that has not passed iteration is referenced non-weakly. - */ -public class WeakConcurrentSet implements Runnable, Iterable { - - final WeakConcurrentMap target; - - public WeakConcurrentSet(Cleaner cleaner) { - switch (cleaner) { - case INLINE: - target = new WeakConcurrentMap.WithInlinedExpunction<>(); - break; - case THREAD: - case MANUAL: - target = new WeakConcurrentMap<>(cleaner == Cleaner.THREAD); - break; - default: - throw new AssertionError(); - } - } - - /** - * @param value The value to add to the set. - * @return {@code true} if the value was added to the set and was not contained before. - */ - public boolean add(V value) { - return target.put(value, Boolean.TRUE) == null; // is null or Boolean.TRUE - } - - /** - * @param value The value to check if it is contained in the set. - * @return {@code true} if the set contains the value. - */ - public boolean contains(V value) { - return target.containsKey(value); - } - - /** - * @param value The value to remove from the set. - * @return {@code true} if the value is contained in the set. - */ - public boolean remove(V value) { - return target.remove(value) != null; // is null or Boolean.TRUE - } - - /** Clears the set. */ - public void clear() { - target.clear(); - } - - /** - * Returns the approximate size of this set where the returned number is at least as big as the - * actual number of entries. - * - * @return The minimum size of this set. - */ - public int approximateSize() { - return target.approximateSize(); - } - - @Override - public void run() { - target.run(); - } - - /** - * Determines the cleaning format. A reference is removed either by an explicitly started cleaner - * thread associated with this instance ({@link Cleaner#THREAD}), as a result of interacting with - * this thread local from any thread ({@link Cleaner#INLINE} or manually by submitting the - * detached thread local to a thread ({@link Cleaner#MANUAL}). - */ - public enum Cleaner { - THREAD, - INLINE, - MANUAL - } - - /** Cleans all unused references. */ - public void expungeStaleEntries() { - target.expungeStaleEntries(); - } - - /** @return The cleaner thread or {@code null} if no such thread was set. */ - public Thread getCleanerThread() { - return target.getCleanerThread(); - } - - @Override - public Iterator iterator() { - return new ReducingIterator<>(target.iterator()); - } - - @Override - public String toString() { - return Collections.newSetFromMap(target.target).toString(); - } - - private static class ReducingIterator implements Iterator { - - private final Iterator> iterator; - - private ReducingIterator(Iterator> iterator) { - this.iterator = iterator; - } - - @Override - public void remove() { - iterator.remove(); - } - - @Override - public V next() { - return iterator.next().getKey(); - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - } -} From 7ad1a12cb2c4cd3ecc19f1354e0bda9301f14be2 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 19:23:42 -0800 Subject: [PATCH 28/30] Enable checkstyle:off comment --- buildscripts/checkstyle.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildscripts/checkstyle.xml b/buildscripts/checkstyle.xml index 64dbb91b9e17..c6716dabf5af 100644 --- a/buildscripts/checkstyle.xml +++ b/buildscripts/checkstyle.xml @@ -373,6 +373,8 @@ + + From 7ae3ab2a868366addbd35ea35490f7c1689505f4 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 20:11:06 -0800 Subject: [PATCH 29/30] Re-generate license report --- .../META-INF/LICENSE | 0 .../META-INF/LICENSE | 0 .../jackson-core-2.12.3.jar/META-INF/NOTICE | 17 ++ .../META-INF/LICENSE | 202 ++++++++++++++++++ .../META-INF/NOTICE | 17 ++ licenses/licenses.md | 135 +++++++----- 6 files changed, 322 insertions(+), 49 deletions(-) rename licenses/{caffeine-3.0.4.jar => jackson-annotations-2.12.3.jar}/META-INF/LICENSE (100%) rename licenses/{opentelemetry-caffeine3-1.8.0-alpha-SNAPSHOT-all.jar => jackson-core-2.12.3.jar}/META-INF/LICENSE (100%) create mode 100644 licenses/jackson-core-2.12.3.jar/META-INF/NOTICE create mode 100644 licenses/jackson-databind-2.12.3.jar/META-INF/LICENSE create mode 100644 licenses/jackson-databind-2.12.3.jar/META-INF/NOTICE diff --git a/licenses/caffeine-3.0.4.jar/META-INF/LICENSE b/licenses/jackson-annotations-2.12.3.jar/META-INF/LICENSE similarity index 100% rename from licenses/caffeine-3.0.4.jar/META-INF/LICENSE rename to licenses/jackson-annotations-2.12.3.jar/META-INF/LICENSE diff --git a/licenses/opentelemetry-caffeine3-1.8.0-alpha-SNAPSHOT-all.jar/META-INF/LICENSE b/licenses/jackson-core-2.12.3.jar/META-INF/LICENSE similarity index 100% rename from licenses/opentelemetry-caffeine3-1.8.0-alpha-SNAPSHOT-all.jar/META-INF/LICENSE rename to licenses/jackson-core-2.12.3.jar/META-INF/LICENSE diff --git a/licenses/jackson-core-2.12.3.jar/META-INF/NOTICE b/licenses/jackson-core-2.12.3.jar/META-INF/NOTICE new file mode 100644 index 000000000000..d226e890daa8 --- /dev/null +++ b/licenses/jackson-core-2.12.3.jar/META-INF/NOTICE @@ -0,0 +1,17 @@ +# Jackson JSON processor + +Jackson is a high-performance, Free/Open Source JSON processing library. +It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has +been in development since 2007. +It is currently developed by a community of developers. + +## Licensing + +Jackson 2.x core and extension components are licensed under Apache License 2.0 +To find the details that apply to this artifact see the accompanying LICENSE file. + +## Credits + +A list of contributors may be found from CREDITS(-2.x) file, which is included +in some artifacts (usually source distributions); but is always available +from the source code management (SCM) system project uses. diff --git a/licenses/jackson-databind-2.12.3.jar/META-INF/LICENSE b/licenses/jackson-databind-2.12.3.jar/META-INF/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/licenses/jackson-databind-2.12.3.jar/META-INF/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/licenses/jackson-databind-2.12.3.jar/META-INF/NOTICE b/licenses/jackson-databind-2.12.3.jar/META-INF/NOTICE new file mode 100644 index 000000000000..d226e890daa8 --- /dev/null +++ b/licenses/jackson-databind-2.12.3.jar/META-INF/NOTICE @@ -0,0 +1,17 @@ +# Jackson JSON processor + +Jackson is a high-performance, Free/Open Source JSON processing library. +It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has +been in development since 2007. +It is currently developed by a community of developers. + +## Licensing + +Jackson 2.x core and extension components are licensed under Apache License 2.0 +To find the details that apply to this artifact see the accompanying LICENSE file. + +## Credits + +A list of contributors may be found from CREDITS(-2.x) file, which is included +in some artifacts (usually source distributions); but is always available +from the source code management (SCM) system project uses. diff --git a/licenses/licenses.md b/licenses/licenses.md index 89bea1691d7b..5049b92b3e96 100644 --- a/licenses/licenses.md +++ b/licenses/licenses.md @@ -1,7 +1,7 @@ #javaagent ##Dependency License Report -_2021-11-09 12:26:05 EET_ +_2021-11-23 20:07:26 PST_ ## Apache License, Version 2.0 **1** **Group:** `com.blogspot.mydailyjava` **Name:** `weak-lock-free` **Version:** `0.18` @@ -14,140 +14,177 @@ _2021-11-09 12:26:05 EET_ > - **POM Project URL**: [https://github.com/raphw/weak-lock-free](https://github.com/raphw/weak-lock-free) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**3** **Group:** `com.github.ben-manes.caffeine` **Name:** `caffeine` **Version:** `3.0.4` +**3** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-annotations` **Version:** `2.12.3` +> - **Project URL**: [http://github.com/FasterXML/jackson](http://github.com/FasterXML/jackson) +> - **Manifest License**: Apache License, Version 2.0 (Not Packaged) +> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) +> - **Embedded license files**: [jackson-annotations-2.12.3.jar/META-INF/LICENSE](jackson-annotations-2.12.3.jar/META-INF/LICENSE) + +**4** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-annotations` **Version:** `2.12.3` +> - **Project URL**: [http://github.com/FasterXML/jackson](http://github.com/FasterXML/jackson) +> - **Manifest License**: Apache License, Version 2.0 (Not Packaged) +> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) +> - **Embedded license files**: [jackson-annotations-2.12.3.jar/META-INF/LICENSE](jackson-annotations-2.12.3.jar/META-INF/LICENSE) + +**5** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-core` **Version:** `2.12.3` +> - **Project URL**: [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) -> - **POM Project URL**: [https://github.com/ben-manes/caffeine](https://github.com/ben-manes/caffeine) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -> - **Embedded license files**: [caffeine-3.0.4.jar/META-INF/LICENSE](caffeine-3.0.4.jar/META-INF/LICENSE) +> - **Embedded license files**: [jackson-core-2.12.3.jar/META-INF/LICENSE](jackson-core-2.12.3.jar/META-INF/LICENSE) + - [jackson-core-2.12.3.jar/META-INF/NOTICE](jackson-core-2.12.3.jar/META-INF/NOTICE) -**4** **Group:** `com.github.ben-manes.caffeine` **Name:** `caffeine` **Version:** `3.0.4` +**6** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-core` **Version:** `2.12.3` +> - **Project URL**: [https://github.com/FasterXML/jackson-core](https://github.com/FasterXML/jackson-core) > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) -> - **POM Project URL**: [https://github.com/ben-manes/caffeine](https://github.com/ben-manes/caffeine) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -> - **Embedded license files**: [caffeine-3.0.4.jar/META-INF/LICENSE](caffeine-3.0.4.jar/META-INF/LICENSE) +> - **Embedded license files**: [jackson-core-2.12.3.jar/META-INF/LICENSE](jackson-core-2.12.3.jar/META-INF/LICENSE) + - [jackson-core-2.12.3.jar/META-INF/NOTICE](jackson-core-2.12.3.jar/META-INF/NOTICE) -**5** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.7.1` +**7** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-databind` **Version:** `2.12.3` +> - **Project URL**: [http://github.com/FasterXML/jackson](http://github.com/FasterXML/jackson) +> - **Manifest License**: Apache License, Version 2.0 (Not Packaged) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) +> - **Embedded license files**: [jackson-databind-2.12.3.jar/META-INF/LICENSE](jackson-databind-2.12.3.jar/META-INF/LICENSE) + - [jackson-databind-2.12.3.jar/META-INF/NOTICE](jackson-databind-2.12.3.jar/META-INF/NOTICE) + +**8** **Group:** `com.fasterxml.jackson.core` **Name:** `jackson-databind` **Version:** `2.12.3` +> - **Project URL**: [http://github.com/FasterXML/jackson](http://github.com/FasterXML/jackson) +> - **Manifest License**: Apache License, Version 2.0 (Not Packaged) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) +> - **Embedded license files**: [jackson-databind-2.12.3.jar/META-INF/LICENSE](jackson-databind-2.12.3.jar/META-INF/LICENSE) + - [jackson-databind-2.12.3.jar/META-INF/NOTICE](jackson-databind-2.12.3.jar/META-INF/NOTICE) + +**9** **Group:** `com.googlecode.concurrentlinkedhashmap` **Name:** `concurrentlinkedhashmap-lru` **Version:** `1.4.2` +> - **Manifest License**: Apache License, Version 2.0 (Not Packaged) +> - **POM Project URL**: [http://code.google.com/p/concurrentlinkedhashmap](http://code.google.com/p/concurrentlinkedhashmap) +> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +**10** **Group:** `com.googlecode.concurrentlinkedhashmap` **Name:** `concurrentlinkedhashmap-lru` **Version:** `1.4.2` +> - **Manifest License**: Apache License, Version 2.0 (Not Packaged) +> - **POM Project URL**: [http://code.google.com/p/concurrentlinkedhashmap](http://code.google.com/p/concurrentlinkedhashmap) +> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +**11** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**6** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api-metrics` **Version:** `1.7.1-alpha` +**12** **Group:** `io.opentelemetry` **Name:** `opentelemetry-api-metrics` **Version:** `1.9.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**7** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.7.1` +**13** **Group:** `io.opentelemetry` **Name:** `opentelemetry-context` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**8** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging` **Version:** `1.7.1` +**14** **Group:** `io.opentelemetry` **Name:** `opentelemetry-exporter-logging` **Version:** `1.9.0` +> - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) +> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +**15** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-aws` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**9** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-aws` **Version:** `1.7.1` +**16** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-kotlin` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**10** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-kotlin` **Version:** `1.7.1` +**17** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-noop-api` **Version:** `1.9.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**11** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-noop-api` **Version:** `1.7.1-alpha` +**18** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-trace-propagators` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**12** **Group:** `io.opentelemetry` **Name:** `opentelemetry-extension-trace-propagators` **Version:** `1.7.1` +**19** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**13** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk` **Version:** `1.7.1` +**20** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-common` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**14** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-common` **Version:** `1.7.1` +**21** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure` **Version:** `1.9.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**15** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure` **Version:** `1.7.1-alpha` +**22** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure-spi` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**16** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-autoconfigure-spi` **Version:** `1.7.1` +**23** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-resources` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**17** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-extension-resources` **Version:** `1.7.1` +**24** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-logs` **Version:** `1.9.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**18** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-metrics` **Version:** `1.7.1-alpha` +**25** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-metrics` **Version:** `1.9.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**19** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-trace` **Version:** `1.7.1` +**26** **Group:** `io.opentelemetry` **Name:** `opentelemetry-sdk-trace` **Version:** `1.9.0` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -**20** **Group:** `io.opentelemetry` **Name:** `opentelemetry-semconv` **Version:** `1.7.1-alpha` +**27** **Group:** `io.opentelemetry` **Name:** `opentelemetry-semconv` **Version:** `1.9.0-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java) > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) -**21** **Group:** `net.bytebuddy` **Name:** `byte-buddy-dep` **Version:** `1.11.22` +**28** **Group:** `net.bytebuddy` **Name:** `byte-buddy-dep` **Version:** `1.11.22` > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) > - **Embedded license files**: [byte-buddy-dep-1.11.22.jar/META-INF/LICENSE](byte-buddy-dep-1.11.22.jar/META-INF/LICENSE) - [byte-buddy-dep-1.11.22.jar/META-INF/NOTICE](byte-buddy-dep-1.11.22.jar/META-INF/NOTICE) -**22** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.2` +**29** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.2` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**23** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.2` +**30** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.2` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -## Embedded - -**24** **Group:** `opentelemetry-java-instrumentation.instrumentation-api-caching` **Name:** `caffeine3` **Version:** `1.8.0-alpha-SNAPSHOT` -> - **Embedded license files**: [opentelemetry-caffeine3-1.8.0-alpha-SNAPSHOT-all.jar/META-INF/LICENSE](opentelemetry-caffeine3-1.8.0-alpha-SNAPSHOT-all.jar/META-INF/LICENSE) - ## MIT License -**25** **Group:** `org.slf4j` **Name:** `slf4j-api` **Version:** `1.7.30` +**31** **Group:** `org.slf4j` **Name:** `slf4j-api` **Version:** `1.7.30` > - **POM Project URL**: [http://www.slf4j.org](http://www.slf4j.org) > - **POM License**: MIT License - [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) -**26** **Group:** `org.slf4j` **Name:** `slf4j-simple` **Version:** `1.7.30` +**32** **Group:** `org.slf4j` **Name:** `slf4j-simple` **Version:** `1.7.30` > - **POM Project URL**: [http://www.slf4j.org](http://www.slf4j.org) > - **POM License**: MIT License - [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT) ## The 3-Clause BSD License -**27** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.2` +**33** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.2` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**28** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.2` +**34** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.2` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**29** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.2` +**35** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.2` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**30** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.2` +**36** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.2` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) @@ -156,28 +193,28 @@ _2021-11-09 12:26:05 EET_ ## Unknown -**31** **Group:** `com.fasterxml.jackson` **Name:** `jackson-bom` **Version:** `2.12.3` +**37** **Group:** `com.fasterxml.jackson` **Name:** `jackson-bom` **Version:** `2.12.3` -**32** **Group:** `com.google.guava` **Name:** `guava-bom` **Version:** `30.1.1-jre` +**38** **Group:** `com.google.guava` **Name:** `guava-bom` **Version:** `30.1.1-jre` -**33** **Group:** `io.opentelemetry` **Name:** `opentelemetry-bom` **Version:** `1.7.1` +**39** **Group:** `io.opentelemetry` **Name:** `opentelemetry-bom` **Version:** `1.9.0` -**34** **Group:** `io.opentelemetry` **Name:** `opentelemetry-bom-alpha` **Version:** `1.7.1-alpha` +**40** **Group:** `io.opentelemetry` **Name:** `opentelemetry-bom-alpha` **Version:** `1.9.0-alpha` -**35** **Group:** `io.opentelemetry.jaxrs-common` **Name:** `bootstrap` **Version:** `1.8.0-alpha-SNAPSHOT` +**41** **Group:** `io.opentelemetry.jaxrs-common` **Name:** `bootstrap` **Version:** `1.9.0-alpha-SNAPSHOT` -**36** **Group:** `io.opentelemetry.kafka-clients-0.11` **Name:** `bootstrap` **Version:** `1.8.0-alpha-SNAPSHOT` +**42** **Group:** `io.opentelemetry.kafka-clients-0.11` **Name:** `bootstrap` **Version:** `1.9.0-alpha-SNAPSHOT` -**37** **Group:** `io.opentelemetry.rmi` **Name:** `bootstrap` **Version:** `1.8.0-alpha-SNAPSHOT` +**43** **Group:** `io.opentelemetry.rmi` **Name:** `bootstrap` **Version:** `1.9.0-alpha-SNAPSHOT` -**38** **Group:** `io.opentelemetry.undertow-1.4` **Name:** `bootstrap` **Version:** `1.8.0-alpha-SNAPSHOT` +**44** **Group:** `io.opentelemetry.undertow-1.4` **Name:** `bootstrap` **Version:** `1.9.0-alpha-SNAPSHOT` -**39** **Group:** `opentelemetry-java-instrumentation` **Name:** `dependencyManagement` **Version:** `1.8.0-alpha-SNAPSHOT` +**45** **Group:** `opentelemetry-java-instrumentation` **Name:** `dependencyManagement` **Version:** `1.9.0-alpha-SNAPSHOT` -**40** **Group:** `org.codehaus.groovy` **Name:** `groovy-bom` **Version:** `2.5.14` +**46** **Group:** `org.codehaus.groovy` **Name:** `groovy-bom` **Version:** `2.5.14` -**41** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-bom` **Version:** `1.4.10` +**47** **Group:** `org.jetbrains.kotlin` **Name:** `kotlin-bom` **Version:** `1.4.10` -**42** **Group:** `org.junit` **Name:** `junit-bom` **Version:** `5.7.2` +**48** **Group:** `org.junit` **Name:** `junit-bom` **Version:** `5.7.2` From 11bee8a91e83b17fded8ba65dbf808fdd99572db Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Nov 2021 20:15:29 -0800 Subject: [PATCH 30/30] Move NOTICE file to package-info.java --- .../api/cache/concurrentlinkedhashmap/NOTICE | 7 ------- .../cache/concurrentlinkedhashmap/package-info.java | 12 ++++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) delete mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/NOTICE diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/NOTICE b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/NOTICE deleted file mode 100644 index e1cedae49576..000000000000 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/NOTICE +++ /dev/null @@ -1,7 +0,0 @@ -ConcurrentLinkedHashMap -Copyright 2008, Ben Manes -Copyright 2010, Google Inc. - -Some alternate data structures provided by JSR-166e -from http://gee.cs.oswego.edu/dl/concurrency-interest/. -Written by Doug Lea and released as Public Domain. diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/package-info.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/package-info.java index 7b331484ddcf..ec971a170cd5 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/package-info.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/cache/concurrentlinkedhashmap/package-info.java @@ -14,6 +14,18 @@ * limitations under the License. */ +/* + * NOTICE file from https://github.com/ben-manes/concurrentlinkedhashmap/blob/master/NOTICE: + * + * ConcurrentLinkedHashMap + * Copyright 2008, Ben Manes + * Copyright 2010, Google Inc. + * + * Some alternate data structures provided by JSR-166e + * from http://gee.cs.oswego.edu/dl/concurrency-interest/. + * Written by Doug Lea and released as Public Domain. + */ + /** * This package contains an implementation of a bounded {@link java.util.concurrent.ConcurrentMap} * data structure.