reactor / BlockHound

Java agent to detect blocking calls from non-blocking threads.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

BlockHound.install() fails with NPE on jdk17 if application is started as java module

dvoloshyn opened this issue · comments

BlockHound.install(); makes jvm exit because of unhandled exception: dynamicThreadPredicate is null.

Steps to Reproduce

  • Use Oracle JDK 17,
  • Create spring-boot application
  • Add module-info.java with required dependencies
  • Use spring-boot-starter-log4j2:2.5.5 and disruptor:3.4.4
  • Use the following VM options:
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector -XX:+AllowRedefinitionToAddDeleteMethods
  • execute BlockHound.install(); inside the static {} section of the main class
  • Put breakpoint into BlockHoundRuntime:62 and run the application from Intellij IDEA
  • Breakpoint will be hit with the following stacktrace:
"Log4j2-TF-1-AsyncLogger[AsyncContext@340f438e]-1@2177" daemon prio=5 tid=0x13 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
	  at reactor.blockhound.BlockHoundRuntime.lambda$static$0(BlockHoundRuntime.java:62)
	  at reactor.blockhound.BlockHoundRuntime$$Lambda$247/0x0000000800c64260.get(Unknown Source:-1)
	  at java.lang.ThreadLocal$SuppliedThreadLocal.initialValue(ThreadLocal.java:305)
	  at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:195)
	  at java.lang.ThreadLocal.get(ThreadLocal.java:172)
	  at reactor.blockhound.BlockHoundRuntime.checkBlocking(BlockHoundRuntime.java:78)
	  at jdk.internal.misc.Unsafe.park(Unsafe.java:-1)
	  at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:252)
	  at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1672)
	  at com.lmax.disruptor.TimeoutBlockingWaitStrategy.waitFor(TimeoutBlockingWaitStrategy.java:38)
	  at com.lmax.disruptor.ProcessingSequenceBarrier.waitFor(ProcessingSequenceBarrier.java:56)
	  at com.lmax.disruptor.BatchEventProcessor.processEvents(BatchEventProcessor.java:159)
	  at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:125)
	  at java.lang.Thread.run(Thread.java:833)
  • Observe that field dynamicThreadPredicate is null. Resuming results in NPE

Your Environment

reactor 3.4.10
blockhound 1.0.6.RELEASE
Windows 10

java 17 2021-09-14 LTS
Java(TM) SE Runtime Environment (build 17+35-LTS-2724)
Java HotSpot(TM) 64-Bit Server VM (build 17+35-LTS-2724, mixed mode, sharing)

Side note found while reproducing: for the agent to attach, here is the module-info.java that I ended up with:

module blockhoundNpe.main {
	requires jdk.attach; //required for bytebuddy to attach
	requires jdk.unsupported; //required for Unsafe access by log4j
	requires reactor.blockhound;
	requires spring.boot;
	requires spring.boot.autoconfigure;
}

Otherwise, an IllegalStateException: No compatible attachment provider is available is thrown by bytebuddy.

I'll need @bsideup's expertise here. It looks like with modules there is either an issue with BlockHoundRuntime being loaded outside the bootstrap classloader, or some initialization change that leads to out-of-order usage vs loading of blockhound...

In any case, BlockhoundRuntime.STATE.withInitial block sees null values for the predicates and the blockingMethodConsumer.

I tried to give these default values, but that renders Blockhound#testInstrumentation non-functional 😞

I met the same issue in open JDK 11. Anyone can support it or fix it? It looks the BlockHound community is inactive now.

0 = {StackTraceElement@3075} "reactor.blockhound.BlockHoundRuntime.lambda$static$0(BlockHoundRuntime.java:62)"
1 = {StackTraceElement@3076} "java.base/java.lang.ThreadLocal$SuppliedThreadLocal.initialValue(ThreadLocal.java:305)"
2 = {StackTraceElement@3077} "java.base/java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:195)"
3 = {StackTraceElement@3078} "java.base/java.lang.ThreadLocal.get(ThreadLocal.java:172)"
4 = {StackTraceElement@3079} "java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)"
5 = {StackTraceElement@3080} "reactor.blockhound.TestThread.(TestThread.java:29)"
6 = {StackTraceElement@3081} "reactor.blockhound.BlockHound$Builder.testInstrumentation(BlockHound.java:462)"
7 = {StackTraceElement@3082} "reactor.blockhound.BlockHound$Builder.install(BlockHound.java:445)"
8 = {StackTraceElement@3083} "reactor.blockhound.BlockHound.install(BlockHound.java:95)"

it seems that there is a work around: since #297, we can now start the blockhound agent using the jvm option -javaagent, which seems to work well in jpms. You don't need to call BlockHound.install() from the code, just start the jvm with the -javaagent:; and blockhound will even be able to locate BlockHoundIntegration plugins declared inside modules (named, un-named, or automatic modules), because ServiceLoader also locates SPIs from modules.

I'm attaching a sample project (see README.MD file), which is using reactor-netty in a jpms environment:

blockhound-jpms.tgz

I'm closing this issue because there is a work around (see previous posted sample demo project).
Feel free to reopen if needed.