Skip to content

Commit

Permalink
add support for Virtual threads
Browse files Browse the repository at this point in the history
Signed-off-by: Ceki Gulcu <ceki@qos.ch>
  • Loading branch information
ceki committed Nov 24, 2023
1 parent 410ad18 commit 9a1fc44
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 44 deletions.
7 changes: 7 additions & 0 deletions logback-core/src/main/java/ch/qos/logback/core/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 10 additions & 1 deletion logback-core/src/main/java/ch/qos/logback/core/ContextBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public class ContextBase implements Context, LifeCycle {
private ScheduledExecutorService scheduledExecutorService;

private ThreadPoolExecutor threadPoolExecutor;
private ExecutorService alternateExecutorService;


protected List<ScheduledFuture<?>> scheduledFutures = new ArrayList<ScheduledFuture<?>>(1);
private LifeCycleManager lifeCycleManager;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ public TimeBasedArchiveRemover(FileNamePattern fileNamePattern, RollingCalendar

int callCount = 0;

public Future<?> cleanAsynchronously(Instant now) {
ArhiveRemoverRunnable runnable = new ArhiveRemoverRunnable(now);

This comment has been minimized.

Copy link
@mhalbritter

mhalbritter Dec 4, 2023

There's a typo in ArhiveRemoverRunnable, should be ArchiveRemoverRunnable.

This comment has been minimized.

Copy link
@ceki

ceki Dec 4, 2023

Author Member

Thank you. Fixed the typo in commit 724efb5

I also marked the class as protected as it is intended for private use or for use by derived classes but reverted to public access in commit d7072d6 because I was not able to grok what protected access actually meant for an instance inner class.

ExecutorService alternateExecutorService = context.getAlternateExecutorService();
Future<?> future = alternateExecutorService.submit(runnable);
return future;
}

/**
* Called from the cleaning thread.
*
* @param now
*/
@Override
public void clean(Instant now) {

Expand Down Expand Up @@ -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;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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()) {
Expand All @@ -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);
}

/**
Expand All @@ -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<Runnable>(), THREAD_FACTORY);
TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), THREAD_FACTORY_FOR_SCHEDULED_EXECUTION_SERVICE);
}

/**
Expand All @@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@

<properties>
<!-- yyyy-MM-dd'T'HH:mm:ss'Z' -->
<project.build.outputTimestamp>2023-08-09T19:41:28Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2023-08-09T19:41:28Z</project.build.outputTimestamp>

<!-- minimal JDK version at runtime -->
<jdk.version>11</jdk.version>
<maven.compiler.release>${jdk.version}</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>4.13.1</junit.version>

<junit-jupiter-api.version>5.9.1</junit-jupiter-api.version>
<junit-vintage-engine.version>5.9.1</junit-vintage-engine.version>
<junit-jupiter-params.version>5.9.1</junit-jupiter-params.version>
Expand Down Expand Up @@ -380,6 +382,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>

<configuration>
<release>${jdk.version}</release>
</configuration>
Expand Down

0 comments on commit 9a1fc44

Please sign in to comment.