diff --git a/feline/src/main/java/com/spotify/feline/Feline.java b/feline/src/main/java/com/spotify/feline/Feline.java
index f31f0f8..72f3bd5 100644
--- a/feline/src/main/java/com/spotify/feline/Feline.java
+++ b/feline/src/main/java/com/spotify/feline/Feline.java
@@ -163,6 +163,24 @@ public static void allowBlockingCallsInside(final String className, final String
});
}
+ /**
+ * The consumer will be called every time a ThreadLocal object triggers initialValue(). This
+ * should be a rare event for well behaving usages of ThreadLocal.
+ *
+ *
However, since Java 17+, the combination of ThreadLocal and ForkJoinPool.commonPool() may
+ * lead to overly frequent calls to initialValue() which can be harmful for performance and/or
+ * correctness. This can be used to detect suspicious high rate of calls.
+ *
+ * @param consumer consumer to be invoked on each call to ThreadLocal.initialValue()
+ */
+ public static void addThreadLocalInitialValueConsumer(final Runnable consumer) {
+ FelineRuntime.addThreadLocalInitialValueConsumer(consumer);
+ }
+
+ public static boolean removeThreadLocalInitialValueConsumer(final Runnable consumer) {
+ return FelineRuntime.removeThreadLocalInitialValueConsumer(consumer);
+ }
+
static {
final Instrumentation instrumentation = ByteBuddyAgent.install();
@@ -198,6 +216,11 @@ public static void allowBlockingCallsInside(final String className, final String
.type(it -> allowances.containsKey(it.getName()))
.transform(new AllowancesTransformer(allowances))
.asTerminalTransformation()
+
+ // instrument ThreadLocal
+ .type(ElementMatchers.isSubTypeOf(ThreadLocal.class))
+ .transform(FelineThreadLocalTransformer.forThreadLocal())
+ .asTerminalTransformation()
.installOn(instrumentation);
}
}
diff --git a/feline/src/main/java/com/spotify/feline/FelineRuntime.java b/feline/src/main/java/com/spotify/feline/FelineRuntime.java
index 7c1b3f0..fba9dfe 100644
--- a/feline/src/main/java/com/spotify/feline/FelineRuntime.java
+++ b/feline/src/main/java/com/spotify/feline/FelineRuntime.java
@@ -29,6 +29,9 @@ public class FelineRuntime {
private static final List>> onExitConsumers =
new CopyOnWriteArrayList<>();
+ private static final List threadLocalInitialValueConsumers =
+ new CopyOnWriteArrayList<>();
+
public static void addOnExitConsumerFirst(
final Consumer