From 9a1fc44f84befa8f05eaeea3f236014a556872e2 Mon Sep 17 00:00:00 2001 From: Ceki Gulcu Date: Fri, 24 Nov 2023 21:06:22 +0100 Subject: [PATCH] add support for Virtual threads Signed-off-by: Ceki Gulcu --- .../java/ch/qos/logback/core/Context.java | 7 +++ .../java/ch/qos/logback/core/ContextBase.java | 11 +++- .../ch/qos/logback/core/CoreConstants.java | 7 ++- .../helper/TimeBasedArchiveRemover.java | 19 ++++-- .../util/AlternateExecutorServiceUtil.java | 24 -------- .../ch/qos/logback/core/util/EnvUtil.java | 8 +++ .../core/util/ExecutorServiceUtil.java | 58 +++++++++++++++++-- ...meBasedRollingWithArchiveRemoval_Test.java | 5 +- pom.xml | 7 ++- 9 files changed, 102 insertions(+), 44 deletions(-) delete mode 100644 logback-core/src/main/java/ch/qos/logback/core/util/AlternateExecutorServiceUtil.java diff --git a/logback-core/src/main/java/ch/qos/logback/core/Context.java b/logback-core/src/main/java/ch/qos/logback/core/Context.java index b78d707b52..0a8b57af70 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/Context.java +++ b/logback-core/src/main/java/ch/qos/logback/core/Context.java @@ -132,6 +132,13 @@ public interface Context extends PropertyContainer { */ ExecutorService getExecutorService(); + /** + * Return an alternate {@link ExecutorService} used for one task per thread execution. + * @return ExecutorService + */ + default ExecutorService getAlternateExecutorService() { + return getExecutorService(); + } /** * Register a component that participates in the context's life cycle. diff --git a/logback-core/src/main/java/ch/qos/logback/core/ContextBase.java b/logback-core/src/main/java/ch/qos/logback/core/ContextBase.java index 62de881efb..96afce2808 100755 --- a/logback-core/src/main/java/ch/qos/logback/core/ContextBase.java +++ b/logback-core/src/main/java/ch/qos/logback/core/ContextBase.java @@ -54,6 +54,8 @@ public class ContextBase implements Context, LifeCycle { private ScheduledExecutorService scheduledExecutorService; private ThreadPoolExecutor threadPoolExecutor; + private ExecutorService alternateExecutorService; + protected List> scheduledFutures = new ArrayList>(1); private LifeCycleManager lifeCycleManager; @@ -225,8 +227,15 @@ public synchronized ExecutorService getExecutorService() { return threadPoolExecutor; } - @Override + public synchronized ExecutorService getAlternateExecutorService() { + if(alternateExecutorService == null) { + alternateExecutorService = ExecutorServiceUtil.newAlternateThreadPoolExecutor(); + } + return alternateExecutorService; + } + + @Override public synchronized ScheduledExecutorService getScheduledExecutorService() { if (scheduledExecutorService == null) { scheduledExecutorService = ExecutorServiceUtil.newScheduledExecutorService(); diff --git a/logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java b/logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java index 6a1114f641..236289dccb 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java +++ b/logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java @@ -29,13 +29,14 @@ public class CoreConstants { */ public static final int CORE_POOL_SIZE = 0; - public static final int SCHEDULED_EXECUTOR_POOL_SIZE = 2; + // In Java 21 and later the actual threads are assumed to be virtual + public static final int SCHEDULED_EXECUTOR_POOL_SIZE = 4; /** * Maximum number of threads to allow in a context's executor service. */ - // if you need a different MAX_POOL_SIZE, please file create a jira issue - // asking to make MAX_POOL_SIZE a parameter. + // if you need a different MAX_POOL_SIZE, please file create a github issue + // asking for a larger MAX_POOL_SIZE parameter. public static final int MAX_POOL_SIZE = 32; // Note that the line.separator property can be looked up even by diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java index a49ec4efb0..2d5819c87d 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java +++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java @@ -47,6 +47,18 @@ public TimeBasedArchiveRemover(FileNamePattern fileNamePattern, RollingCalendar int callCount = 0; + public Future cleanAsynchronously(Instant now) { + ArhiveRemoverRunnable runnable = new ArhiveRemoverRunnable(now); + ExecutorService alternateExecutorService = context.getAlternateExecutorService(); + Future future = alternateExecutorService.submit(runnable); + return future; + } + + /** + * Called from the cleaning thread. + * + * @param now + */ @Override public void clean(Instant now) { @@ -233,12 +245,7 @@ public String toString() { return "c.q.l.core.rolling.helper.TimeBasedArchiveRemover"; } - public Future cleanAsynchronously(Instant now) { - ArhiveRemoverRunnable runnable = new ArhiveRemoverRunnable(now); - ExecutorService executorService = context.getExecutorService(); - Future future = executorService.submit(runnable); - return future; - } + public class ArhiveRemoverRunnable implements Runnable { Instant now; diff --git a/logback-core/src/main/java/ch/qos/logback/core/util/AlternateExecutorServiceUtil.java b/logback-core/src/main/java/ch/qos/logback/core/util/AlternateExecutorServiceUtil.java deleted file mode 100644 index d45f3a290c..0000000000 --- a/logback-core/src/main/java/ch/qos/logback/core/util/AlternateExecutorServiceUtil.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2023, QOS.ch. All rights reserved. - * - * This program and the accompanying materials are dual-licensed under - * either the terms of the Eclipse Public License v1.0 as published by - * the Eclipse Foundation - * - * or (per the licensee's choosing) - * - * under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation. - */ - -package ch.qos.logback.core.util; - -import java.util.concurrent.ExecutorService; - -public class AlternateExecutorServiceUtil { - - static public ExecutorService newThreadPoolExecutor() { - return ExecutorServiceUtil.newThreadPoolExecutor(); - } -} diff --git a/logback-core/src/main/java/ch/qos/logback/core/util/EnvUtil.java b/logback-core/src/main/java/ch/qos/logback/core/util/EnvUtil.java index 511e8069f4..a6ccd470d8 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/util/EnvUtil.java +++ b/logback-core/src/main/java/ch/qos/logback/core/util/EnvUtil.java @@ -108,6 +108,14 @@ static public boolean isJDK18OrHigher() { return isJDK_N_OrHigher(18); } + /** + * @since logback 1.3.12/1.4.12 + * @return true if runtime JDK is version 21 or higher + */ + static public boolean isJDK21OrHigher() { + return isJDK_N_OrHigher(21); + } + static public boolean isJaninoAvailable() { ClassLoader classLoader = EnvUtil.class.getClassLoader(); try { diff --git a/logback-core/src/main/java/ch/qos/logback/core/util/ExecutorServiceUtil.java b/logback-core/src/main/java/ch/qos/logback/core/util/ExecutorServiceUtil.java index 72c15671c7..0ffb6464a9 100755 --- a/logback-core/src/main/java/ch/qos/logback/core/util/ExecutorServiceUtil.java +++ b/logback-core/src/main/java/ch/qos/logback/core/util/ExecutorServiceUtil.java @@ -13,6 +13,8 @@ */ package ch.qos.logback.core.util; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -33,11 +35,39 @@ */ public class ExecutorServiceUtil { - private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() { + static private final String NEW_VIRTUAL_TPT_METHOD_NAME = "newVirtualThreadPerTaskExecutor"; + + private static final ThreadFactory THREAD_FACTORY_FOR_SCHEDULED_EXECUTION_SERVICE = new ThreadFactory() { - private final ThreadFactory defaultFactory = Executors.defaultThreadFactory(); private final AtomicInteger threadNumber = new AtomicInteger(1); + + private final ThreadFactory defaultFactory = makeThreadFactory(); + + /** + * A thread factory which may be a virtual thread factory if available. + * + * @return + */ + private ThreadFactory makeThreadFactory() { + if(EnvUtil.isJDK21OrHigher()) { + try { + Method ofVirtualMethod = Thread.class.getMethod("ofVirtual"); + Object threadBuilderOfVirtual = ofVirtualMethod.invoke(null); + Method factoryMethod = threadBuilderOfVirtual.getClass().getMethod("factory"); + System.out.println("virtual THREAD FACTORY"); + return (ThreadFactory) factoryMethod.invoke(threadBuilderOfVirtual); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return Executors.defaultThreadFactory(); + } + + } else { + System.out.println("default THREAD FACTORY"); + return Executors.defaultThreadFactory(); + } + } + + @Override public Thread newThread(Runnable r) { Thread thread = defaultFactory.newThread(r); if (!thread.isDaemon()) { @@ -49,7 +79,8 @@ public Thread newThread(Runnable r) { }; static public ScheduledExecutorService newScheduledExecutorService() { - return new ScheduledThreadPoolExecutor(CoreConstants.SCHEDULED_EXECUTOR_POOL_SIZE, THREAD_FACTORY); + return new ScheduledThreadPoolExecutor(CoreConstants.SCHEDULED_EXECUTOR_POOL_SIZE, + THREAD_FACTORY_FOR_SCHEDULED_EXECUTION_SERVICE); } /** @@ -68,7 +99,7 @@ static public ExecutorService newExecutorService() { */ static public ThreadPoolExecutor newThreadPoolExecutor() { return new ThreadPoolExecutor(CoreConstants.CORE_POOL_SIZE, CoreConstants.MAX_POOL_SIZE, 0L, - TimeUnit.MILLISECONDS, new SynchronousQueue(), THREAD_FACTORY); + TimeUnit.MILLISECONDS, new SynchronousQueue(), THREAD_FACTORY_FOR_SCHEDULED_EXECUTION_SERVICE); } /** @@ -83,4 +114,23 @@ static public void shutdown(ExecutorService executorService) { } } + /** + * An alternate implementation of {@linl #newThreadPoolExecutor} which returns a virtual thread per task executor when + * available. + * + * @since 1.3.12/1.4.12 + */ + static public ExecutorService newAlternateThreadPoolExecutor() { + + if(EnvUtil.isJDK21OrHigher()) { + try { + Method newVirtualTPTMethod = Executors.class.getMethod(NEW_VIRTUAL_TPT_METHOD_NAME); + return (ExecutorService) newVirtualTPTMethod.invoke(null); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return newThreadPoolExecutor(); + } + } else { + return newThreadPoolExecutor(); + } + } } diff --git a/logback-core/src/test/java/ch/qos/logback/core/rolling/TimeBasedRollingWithArchiveRemoval_Test.java b/logback-core/src/test/java/ch/qos/logback/core/rolling/TimeBasedRollingWithArchiveRemoval_Test.java index 9b71021844..00a1199190 100755 --- a/logback-core/src/test/java/ch/qos/logback/core/rolling/TimeBasedRollingWithArchiveRemoval_Test.java +++ b/logback-core/src/test/java/ch/qos/logback/core/rolling/TimeBasedRollingWithArchiveRemoval_Test.java @@ -22,7 +22,6 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -33,10 +32,8 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.time.temporal.ChronoUnit; -//import org.joda.time.DateTimeZone; -//import org.joda.time.Days; -//import org.joda.time.LocalDate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; diff --git a/pom.xml b/pom.xml index c6118a09cb..8b5056fe92 100755 --- a/pom.xml +++ b/pom.xml @@ -49,11 +49,13 @@ - 2023-08-09T19:41:28Z + 2023-08-09T19:41:28Z + + 11 ${jdk.version} UTF-8 - 4.13.1 + 5.9.1 5.9.1 5.9.1 @@ -380,6 +382,7 @@ org.apache.maven.plugins maven-compiler-plugin + ${jdk.version}