Repository: quarkusio/quarkus
Issue: dev-mode Classloader leaks via the vertx blocking executor pool

### Describe the bug

The code in https://github.com/quarkusio/quarkus/blob/fadd877ddbe5dd32c57b0a2b67a342367714f7dd/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java#L129 isn't really doing what it's supposed to do as all tasks being passed to `executeBlocking` are being run within the same context, and with ordering requested so they are guaranteed to be associated to a single thread and then run in order.

I have a fix coming.


Changed files:
extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java

diff --git a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java
index ed799c7014c..c7c0357c730 100644
--- a/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java
+++ b/extensions/vertx-core/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java
@@ -7,6 +7,8 @@
 import static io.quarkus.vertx.core.runtime.SSLConfigHelper.configurePfxTrustOptions;
 
 import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -25,6 +27,7 @@
 
 import org.jboss.logging.Logger;
 import org.jboss.threads.ContextHandler;
+import org.jboss.threads.EnhancedQueueExecutor;
 import org.wildfly.common.cpu.ProcessorInfo;
 
 import io.netty.channel.EventLoopGroup;
@@ -41,7 +44,6 @@
 import io.vertx.core.AsyncResult;
 import io.vertx.core.Context;
 import io.vertx.core.Handler;
-import io.vertx.core.Promise;
 import io.vertx.core.Vertx;
 import io.vertx.core.VertxOptions;
 import io.vertx.core.dns.AddressResolverOptions;
@@ -53,6 +55,7 @@
 import io.vertx.core.impl.VertxBuilder;
 import io.vertx.core.impl.VertxImpl;
 import io.vertx.core.impl.VertxThread;
+import io.vertx.core.impl.WorkerPool;
 import io.vertx.core.spi.VertxThreadFactory;
 import io.vertx.core.spi.resolver.ResolverProvider;
 
@@ -129,19 +132,14 @@ public void handle(AsyncResult<Void> event) {
     private void tryCleanTccl(Vertx devModeVertx) {
         //this is a best effort attempt to clean out the old TCCL from
         ClassLoader cl = Thread.currentThread().getContextClassLoader();
-        for (int i = 0; i < blockingThreadPoolSize; ++i) {
-            devModeVertx.executeBlocking(new Handler<Promise<Object>>() {
-                @Override
-                public void handle(Promise<Object> event) {
-                    Thread.currentThread().setContextClassLoader(cl);
-                    try {
-                        Thread.sleep(50);
-                    } catch (InterruptedException e) {
 
-                    }
-                }
-            }, null);
+        EnhancedQueueExecutor executor = extractExecutor(devModeVertx);
+
+        final Thread[] runningThreads = executor.getRunningThreads();
+        for (Thread t : runningThreads) {
+            t.setContextClassLoader(cl);
         }
+
         EventLoopGroup group = ((VertxImpl) devModeVertx).getEventLoopGroup();
         for (EventExecutor i : group) {
             i.execute(new Runnable() {
@@ -156,6 +154,30 @@ public void run() {
 
     }
 
+    /**
+     * Extract the JBoss Threads EnhancedQueueExecutor from the Vertx instance
+     * this is messy as it needs to use reflection until Vertx can expose it.
+     */
+    private EnhancedQueueExecutor extractExecutor(Vertx devModeVertx) {
+        final ContextInternal ctx = (ContextInternal) devModeVertx.getOrCreateContext();
+        final WorkerPool workerPool = ctx.workerPool();
+        final Method executorMethod;
+        try {
+            executorMethod = WorkerPool.class.getDeclaredMethod("executor");
+            executorMethod.setAccessible(true);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+        final Object result;
+        try {
+            result = executorMethod.invoke(workerPool);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException(e);
+        }
+        EnhancedQueueExecutor executor = (EnhancedQueueExecutor) result;
+        return executor;
+    }
+
     public IOThreadDetector detector() {
         return new IOThreadDetector() {
             @Override