From 4bba0bb2536a4e3cff67a7cd6ddb635d9a56ac7e Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Tue, 26 Oct 2021 13:39:18 +1100 Subject: [PATCH] Use custom thread factory for NIO NIO async IO uses a custom thread pool, this allows us to update the TCCL in dev mode. Fixes #20973 (cherry picked from commit 6c708fe09516e87c2b81ab020458f48db3015b02) --- .../deployment/dev/IsolatedDevModeMain.java | 3 ++ .../deployment/dev/IsolatedTestModeMain.java | 2 + .../dev/io/NioThreadPoolDevModeProcessor.java | 21 +++++++++ .../dev/io/NioThreadPoolThreadFactory.java | 45 +++++++++++++++++++ .../runtime/dev/io/NioThreadPoolRecorder.java | 19 ++++++++ 5 files changed, 90 insertions(+) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/dev/io/NioThreadPoolDevModeProcessor.java create mode 100644 core/devmode-spi/src/main/java/io/quarkus/dev/io/NioThreadPoolThreadFactory.java create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/dev/io/NioThreadPoolRecorder.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java index bf62d68499827..5b8821c82a21b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedDevModeMain.java @@ -360,6 +360,9 @@ public void close() { //the main entry point, but loaded inside the augmentation class loader @Override public void accept(CuratedApplication o, Map params) { + //setup the dev mode thread pool for NIO + System.setProperty("java.nio.channels.DefaultThreadPool.threadFactory", + "io.quarkus.dev.io.NioThreadPoolThreadFactory"); Timing.staticInitStarted(o.getBaseRuntimeClassLoader(), false); //https://github.com/quarkusio/quarkus/issues/9748 //if you have an app with all daemon threads then the app thread diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedTestModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedTestModeMain.java index d41a827ab7c51..dce3eb0cfc3cb 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedTestModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedTestModeMain.java @@ -103,6 +103,8 @@ public void close() { //the main entry point, but loaded inside the augmentation class loader @Override public void accept(CuratedApplication o, Map params) { + System.setProperty("java.nio.channels.DefaultThreadPool.threadFactory", + "io.quarkus.dev.io.NioThreadPoolThreadFactory"); Timing.staticInitStarted(o.getBaseRuntimeClassLoader(), false); try { curatedApplication = o; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/io/NioThreadPoolDevModeProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/io/NioThreadPoolDevModeProcessor.java new file mode 100644 index 0000000000000..44375b5fbdbec --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/io/NioThreadPoolDevModeProcessor.java @@ -0,0 +1,21 @@ +package io.quarkus.deployment.dev.io; + +import io.quarkus.deployment.IsNormal; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Produce; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.dev.testing.TestSetupBuildItem; +import io.quarkus.runtime.dev.io.NioThreadPoolRecorder; + +public class NioThreadPoolDevModeProcessor { + + @Produce(TestSetupBuildItem.class) + @BuildStep(onlyIfNot = IsNormal.class) + @Record(ExecutionTime.STATIC_INIT) + void setupTCCL(NioThreadPoolRecorder recorder, ShutdownContextBuildItem shutdownContextBuildItem) { + recorder.updateTccl(shutdownContextBuildItem); + } + +} diff --git a/core/devmode-spi/src/main/java/io/quarkus/dev/io/NioThreadPoolThreadFactory.java b/core/devmode-spi/src/main/java/io/quarkus/dev/io/NioThreadPoolThreadFactory.java new file mode 100644 index 0000000000000..e52e1d2c24532 --- /dev/null +++ b/core/devmode-spi/src/main/java/io/quarkus/dev/io/NioThreadPoolThreadFactory.java @@ -0,0 +1,45 @@ +package io.quarkus.dev.io; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicReference; + +/** + * We need to be able to update the TCCL of the NIO default thread pool in dev/test mode + *

+ * This class lets us to that. + */ +public class NioThreadPoolThreadFactory implements ThreadFactory { + + private static final CopyOnWriteArrayList allThreads = new CopyOnWriteArrayList<>(); + private static volatile ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); + + @Override + public Thread newThread(Runnable r) { + AtomicReference t = new AtomicReference<>(); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + r.run(); + } finally { + allThreads.remove(t.get()); + } + } + }, "NIO IO Thread (created by Quarkus)"); + t.set(thread); + thread.setDaemon(true); + allThreads.add(thread); + thread.setContextClassLoader(currentCl); + return thread; + } + + public static ClassLoader updateTccl(ClassLoader cl) { + ClassLoader old = currentCl; + currentCl = cl; + for (Thread i : allThreads) { + i.setContextClassLoader(cl); + } + return old; + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/dev/io/NioThreadPoolRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/dev/io/NioThreadPoolRecorder.java new file mode 100644 index 0000000000000..c14b2829d6731 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/dev/io/NioThreadPoolRecorder.java @@ -0,0 +1,19 @@ +package io.quarkus.runtime.dev.io; + +import io.quarkus.dev.io.NioThreadPoolThreadFactory; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class NioThreadPoolRecorder { + + public void updateTccl(ShutdownContext context) { + ClassLoader old = NioThreadPoolThreadFactory.updateTccl(Thread.currentThread().getContextClassLoader()); + context.addLastShutdownTask(new Runnable() { + @Override + public void run() { + NioThreadPoolThreadFactory.updateTccl(old); + } + }); + } +}