Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop instrumentation-api-caching module and move weak cache implementation to instrumentation-api #4667

Merged
merged 33 commits into from
Nov 24, 2021

Conversation

iNikem
Copy link
Contributor

@iNikem iNikem commented Nov 18, 2021

This if first draft PR to verify my understand of what we agreed upon on Tuesday.

We have couple of usages that still don't work with WeakLockFreeCache:

  • FutureListenerWrappers wants weak values
  • ClassLoaderHasClassesNamedMatcher and AgentCachingPoolStrategy want to specify max capacity

How did we want to solve that?

@iNikem
Copy link
Contributor Author

iNikem commented Nov 18, 2021

The bigger problem is AgentCachingPoolStrategy.SharedResolutionCacheAdapter.find, which currently cannot work with comparison-based cache keys

@mateuszrzeszutek
Copy link
Member

I think FutureListenerWrappers should actually work correctly with a VirtualField now - a couple of weeks ago @laurit has introduced a lambda instrumentation that makes them go through ClassFileTransformer.

private final Cache<ClassLoader, Boolean> cache =
Cache.builder().setWeakKeys().setMaximumSize(25).build();
Cache.builder().build();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could just remove the max size here? This usage is very similar to the one in MuzzleMatcher, and we don't have any limit there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for just weak keys (no max) here

* Keys are always referenced weakly and are compared using identity comparison, not
* {@link Object#equals(Object)}.
*/
public interface Cache<K, V> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep the name Cache? It's now just a weak-keyed map, since we removed caffeine et al.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not want to rename in this PR, to avoid too many changes at once. We can rename later, if we want

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now there are two different implementations, so we better keep this generic name

@iNikem
Copy link
Contributor Author

iNikem commented Nov 19, 2021

Only 2 problems remain.

One is simple: SqlStatementSanitizer wants to use a bounded cache. I am not sure if bringing in the whole CLHM just for one use case makes sense.

Another one is more complicated: AgentCachingPoolStrategy.SharedResolutionCacheAdapter relies on cache keys being compared by equal. But I don't totally grasp why do we need this custom implementation of ByteBuddy cache at all.

@trask trask force-pushed the remove-cache-from-api branch from 6ec3420 to 830e1d2 Compare November 21, 2021 03:15
@trask
Copy link
Member

trask commented Nov 21, 2021

SqlStatementSanitizer wants to use a bounded cache. I am not sure if bringing in the whole CLHM just for one use case makes sense.

This is an important cache for an important use case (jdbc). I'm not sure what alternative we have? I think LRU (CLHM) should be enough, not sure if we need more advanced LFU eviction (caffeine), which would avoid the complexity of caffeine2/3 for now at least (in both library and agent).

Another one is more complicated: AgentCachingPoolStrategy.SharedResolutionCacheAdapter relies on cache keys being compared by equal. But I don't totally grasp why do we need this custom implementation of ByteBuddy cache at all.

My guess based on prior work is that this is used to avoid re-analyzing classes during instrumentation (e.g. looking up structure/hierarchy of parent class/interfaces). This can help a lot with startup overhead. I recall the DataDog folks spent a good amount of time tuning this particular cache. I'd be hesitant to mess with it without doing a good amount of testing. I think CLHM should be good here also (no need for more advanced eviction).

Cache.builder().setMaximumSize(32).build();
private static final Cache<String, AttributeKey<List<String>>> responseKeysCache =
Cache.builder().setMaximumSize(32).build();
private static final ConcurrentHashMap<String, AttributeKey<List<String>>> requestKeysCache =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I replace this with bounded cache now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are naturally limited, I just pushed comment to this file

@iNikem
Copy link
Contributor Author

iNikem commented Nov 21, 2021

CLHM uses unsafe and thus fails our jpms tests. Should I switch to cache2k? I am a little bit concerned by its size, 400K is not that small.

Cache.builder().setMaximumSize(32).build();
private static final Cache<String, AttributeKey<List<String>>> responseKeysCache =
Cache.builder().setMaximumSize(32).build();
private static final ConcurrentHashMap<String, AttributeKey<List<String>>> requestKeysCache =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are naturally limited, I just pushed comment to this file

Comment on lines 28 to 29
implementation("com.blogspot.mydailyjava:weak-lock-free")
implementation("com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's shade these in. less transitive dependencies will make instrumentation-api more palatable for libraries to embed directly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we copied in CLHM in order to update code to use default CHM

I copied in weak-lock-free also (instead of shading), just to avoid gradle shadow problems (only 4 files)

@trask
Copy link
Member

trask commented Nov 21, 2021

CLHM uses unsafe and thus fails our jpms tests. Should I switch to cache2k? I am a little bit concerned by its size, 400K is not that small.

I keep forgetting all the branches of this problem 😞

This is what using Caffeine 3 on Java 11 would solve for us.

Cache2k seems like a reasonable option to keep things simple for now.

As long as it's all hidden / shaded dependency we can improve on it later.

@ben-manes
Copy link

CLHM has a backport of CHM v8 which uses unsafe. Most just copy the code into their project and replace with the standard CHM. For example ms-jdbc, groovy, micronaut. This is pretty much equivalent to shading.

@trask
Copy link
Member

trask commented Nov 22, 2021

CLHM has a backport of CHM v8 which uses unsafe. Most just copy the code into their project and replace with the standard CHM. For example ms-jdbc, groovy, micronaut. This is pretty much equivalent to shading.

thx @ben-manes! 👍

}
return new WeakReference<>(wrapper);
});
return resultReference.get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we may need to be careful here, I think the cache can return a "cleared" weak value reference now (previously caffeine took care of not making "cleared" values visible).

Instead, I think we could use VirtualField instead of Cache, essentially reverting #3059 now that #4182 solves the underlying problem with VirtualField not being applied to lambdas.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sent two commits converting from Cache to VirtualField

The second commit was needed to workaround a problem caused by the VirtualField instrumenting some of our own helper class lambdas. Normally I don't think helper classes are instrumented since they are injected from inside of ClassFileTransformer.transform (which is not recursive). But lambdas are different since they don't go through the normal class definition / transformation process.

I was already having second thoughts about the first commit above after typing up the warning about the memory leak that will occur if this code is ever used inside of library instrumentation.

I did add a commit to handle cleared weak values.

@trask trask marked this pull request as ready for review November 24, 2021 01:14
@@ -0,0 +1,7 @@
ConcurrentLinkedHashMap
Copyright 2008, Ben Manes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add this line above the Copyright 2010 Google Inc. into the file license headers. We could probably remove the NOTICE then (otherwise I would put the NOTICE in resources, not java)

Copy link
Contributor

@anuraaga anuraaga Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way I noticed micronaut doesn't reference the NOTICE, nor update license headers

https://github.com/micronaut-projects/micronaut-core/tree/813923882bcfed158d136a27bf8010edf70bb613/core/src/main/java/io/micronaut/core/util/clhm

IANAL, but I think the author tag satisfies "readable copy of attribution notices contained within such NOTICE file...within the Source form or documentation"

https://github.com/ben-manes/concurrentlinkedhashmap/blob/master/LICENSE#L111

Mostly not a fan of having license type files within the java source - converting to a docstring on a package-info.java would also be better to me if needing to include it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

converting to a docstring on a package-info.java would also be better to me if needing to include it

went with this 👍

I also re-generated the license report, which pulls in NOTICE files if they're in the dependencies jar file, but in this case it's not.

Copy link
Contributor

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just point about NOTICE but not a huge deal

// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively we could add an option to use weak values when VirtualField fallback map is used. When we know that VirtualField value references key we could just use VirtualField with weak values and would not need to worry whether it is going to use fallback map because something wasn't instrumented or because it is used inside library instrumentation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll open a issue for this 👍

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<ClassLoader> {

private final Cache<ClassLoader, Boolean> cache =
Cache.builder().setWeakKeys().setMaximumSize(25).build();
private final Cache<ClassLoader, Boolean> cache = Cache.weak();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this, in my opinion 25 was way too low.

@trask trask merged commit 821a4b8 into open-telemetry:main Nov 24, 2021
@iNikem iNikem deleted the remove-cache-from-api branch November 25, 2021 08:26

import io.opentelemetry.instrumentation.api.cache.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put CLHM and WeakLockFree in internal package(s)? They shouldn't be a part of our public API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 created #4735 to track

RashmiRam pushed a commit to RashmiRam/opentelemetry-auto-instr-java that referenced this pull request May 23, 2022
…ation to instrumentation-api (open-telemetry#4667)

* Drop instrumentation-api-caching module and move weak cache implementation to instrumentation-api

* Some test fixes

* Some cleanup

* Temporary workaround for using weak values in FutureListenerWrappers

* Spotless

* Update ClassNames and SpanNames

* Compilation and comment

* Add bounded cache and clean interface

* Polish

* Add comment

* Vendor ConcurrentLinkedHashMap in

* Let errorprone ignore vendored CLHM for now

* Keep license in java files too

* Convert Netty wrapper cache to VirtualField

* Work around lambda instrumentation failure

Ideally we would ignore instrumenting helper classes...

* Revert "Work around lambda instrumentation failure"

This reverts commit 6d63815.

* Revert "Convert Netty wrapper cache to VirtualField"

This reverts commit dac1522.

* Handle cleared weak values

* Fix comment

* Delete instrumentation-api-caching

* Copy in weak-lock-free

* Remove caffeine remnants

* Fix checkstyle

* Rename BoundedCache to MapBackedCached

* Remove duplicate LICENSE

* Remove outdated comment

* Sync with SDK copy of weaklockfree

* Enable checkstyle:off comment

* Re-generate license report

* Move NOTICE file to package-info.java

Co-authored-by: Trask Stalnaker <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants