ClassLoaderHasClassesNamedMatcher.java 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. /*
  2. * Copyright The OpenTelemetry Authors
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package io.opentelemetry.javaagent.extension.matcher;
  6. import io.opentelemetry.instrumentation.api.internal.cache.Cache;
  7. import io.opentelemetry.javaagent.bootstrap.internal.ClassLoaderMatcherCacheHolder;
  8. import io.opentelemetry.javaagent.bootstrap.internal.InClassLoaderMatcher;
  9. import java.util.BitSet;
  10. import java.util.List;
  11. import java.util.concurrent.CopyOnWriteArrayList;
  12. import java.util.concurrent.atomic.AtomicInteger;
  13. import java.util.concurrent.locks.Lock;
  14. import java.util.concurrent.locks.ReentrantReadWriteLock;
  15. import net.bytebuddy.matcher.ElementMatcher;
  16. class ClassLoaderHasClassesNamedMatcher extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
  17. // caching is disabled for build time muzzle checks
  18. // this field is set via reflection from ClassLoaderMatcher
  19. static boolean useCache = true;
  20. private static final AtomicInteger counter = new AtomicInteger();
  21. private final String[] resources;
  22. // each matcher gets a unique index that is used for caching the matching status
  23. private final int index = counter.getAndIncrement();
  24. ClassLoaderHasClassesNamedMatcher(String... classNames) {
  25. resources = classNames;
  26. for (int i = 0; i < resources.length; i++) {
  27. resources[i] = resources[i].replace(".", "/") + ".class";
  28. }
  29. if (useCache) {
  30. Manager.INSTANCE.add(this);
  31. }
  32. }
  33. @Override
  34. public boolean matches(ClassLoader cl) {
  35. if (cl == null) {
  36. // Can't match the bootstrap class loader.
  37. return false;
  38. }
  39. if (useCache) {
  40. return Manager.INSTANCE.match(this, cl);
  41. } else {
  42. return hasResources(cl, resources);
  43. }
  44. }
  45. private static boolean hasResources(ClassLoader cl, String... resources) {
  46. boolean priorValue = InClassLoaderMatcher.getAndSet(true);
  47. try {
  48. for (String resource : resources) {
  49. if (cl.getResource(resource) == null) {
  50. return false;
  51. }
  52. }
  53. } finally {
  54. InClassLoaderMatcher.set(priorValue);
  55. }
  56. return true;
  57. }
  58. private static class Manager {
  59. static final Manager INSTANCE = new Manager();
  60. private final List<ClassLoaderHasClassesNamedMatcher> matchers = new CopyOnWriteArrayList<>();
  61. // each matcher gets a two bits in BitSet, that first bit indicates whether current matcher has
  62. // been run for given class loader and the second whether it matched or not
  63. private final Cache<ClassLoader, BitSet> enabled = Cache.weak();
  64. private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  65. private final Lock readLock = lock.readLock();
  66. private final Lock writeLock = lock.writeLock();
  67. private volatile boolean matchCalled = false;
  68. Manager() {
  69. ClassLoaderMatcherCacheHolder.addCache(enabled);
  70. }
  71. void add(ClassLoaderHasClassesNamedMatcher matcher) {
  72. if (matchCalled) {
  73. throw new IllegalStateException("All matchers should be create before match is called");
  74. }
  75. matchers.add(matcher);
  76. }
  77. boolean match(ClassLoaderHasClassesNamedMatcher matcher, ClassLoader cl) {
  78. matchCalled = true;
  79. BitSet set = enabled.computeIfAbsent(cl, (unused) -> new BitSet(counter.get() * 2));
  80. int matcherRunBit = 2 * matcher.index;
  81. int matchedBit = matcherRunBit + 1;
  82. readLock.lock();
  83. try {
  84. if (!set.get(matcherRunBit)) {
  85. // read lock needs to be released before upgrading to write lock
  86. readLock.unlock();
  87. // we do the resource presence check outside the lock to keep the time we need to hold
  88. // the write lock minimal
  89. boolean matches = hasResources(cl, matcher.resources);
  90. writeLock.lock();
  91. try {
  92. if (!set.get(matcherRunBit)) {
  93. if (matches) {
  94. set.set(matchedBit);
  95. }
  96. set.set(matcherRunBit);
  97. }
  98. } finally {
  99. // downgrading the write lock to the read lock
  100. readLock.lock();
  101. writeLock.unlock();
  102. }
  103. }
  104. return set.get(matchedBit);
  105. } finally {
  106. readLock.unlock();
  107. }
  108. }
  109. }
  110. }