Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
import io.netty5.channel.nio.NioIoHandler;
import io.netty5.channel.socket.nio.NioServerSocketChannel;
import io.netty5.channel.socket.nio.NioSocketChannel;
import java.util.concurrent.ThreadFactory;
import java.util.function.Supplier;
import lombok.NonNull;
import org.jetbrains.annotations.Nullable;

/**
* Holds all supported transport types and functionality to retrieve model instances for servers/clients construction.
Expand Down Expand Up @@ -131,12 +133,13 @@ public enum NettyTransport {
/**
* Creates a new event loop group of the current selected transport with the supplied amount of threads.
*
* @param threads the amount of threads.
* @param threads the amount of threads.
* @param threadFactory the thread factory to use for event loop threads, null to use the default factory.
* @return a new event loop group for this transport.
* @throws IllegalArgumentException if the given number of threads is negative.
*/
public @NonNull EventLoopGroup createEventLoopGroup(int threads) {
return new MultithreadEventLoopGroup(threads, this.ioHandlerFactory.get());
public @NonNull EventLoopGroup createEventLoopGroup(int threads, @Nullable ThreadFactory threadFactory) {
return new MultithreadEventLoopGroup(threads, threadFactory, this.ioHandlerFactory.get());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@
import io.netty5.channel.Channel;
import io.netty5.channel.ChannelFactory;
import io.netty5.channel.EventLoopGroup;
import io.netty5.channel.MultithreadEventLoopGroup;
import io.netty5.channel.ServerChannel;
import io.netty5.channel.ServerChannelFactory;
import io.netty5.handler.codec.DecoderException;
import io.netty5.handler.ssl.OpenSsl;
import io.netty5.handler.ssl.SslProvider;
import io.netty5.util.ResourceLeakDetector;
import io.netty5.util.concurrent.DefaultThreadFactory;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import lombok.NonNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -123,7 +126,8 @@ private static int overriddenCountOrDefault(int overriddenSetting, int defaultVa
var maximumPoolSize = overriddenCountOrDefault(PACKET_DISPATCH_THREADS, defaultEnvThreadCount);

var threadFactory = new ThreadFactoryBuilder()
.setNameFormat("Packet-Dispatcher-%d")
.setDaemon(true)
.setNameFormat("CloudNet-Packet-Dispatcher-%d")
.setThreadFactory(Executors.defaultThreadFactory())
.build();
return new ScalingNetworkTaskScheduler(threadFactory, maximumPoolSize);
Expand All @@ -136,7 +140,7 @@ private static int overriddenCountOrDefault(int overriddenSetting, int defaultVa
* @return a newly created boss event loop group.
*/
public static @NonNull EventLoopGroup createBossEventLoopGroup() {
return SELECTED_NETTY_TRANSPORT.createEventLoopGroup(1);
return SELECTED_NETTY_TRANSPORT.createEventLoopGroup(1, createEventLoopThreadFactory());
}

/**
Expand All @@ -154,7 +158,7 @@ private static int overriddenCountOrDefault(int overriddenSetting, int defaultVa
// TODO: consider moving the default thread amount for an environment into the environment as a property
var defaultEnvThreadCount = driverEnvironment.equals(DriverEnvironment.NODE) ? 6 : 2;
var threadCount = overriddenCountOrDefault(NETTY_EVENT_LOOP_THREADS, defaultEnvThreadCount);
return SELECTED_NETTY_TRANSPORT.createEventLoopGroup(threadCount);
return SELECTED_NETTY_TRANSPORT.createEventLoopGroup(threadCount, createEventLoopThreadFactory());
}

/**
Expand Down Expand Up @@ -283,4 +287,14 @@ public static int varIntBytes(int value) {
public static @NonNull BufferAllocator selectedBufferAllocator() {
return SELECTED_BUFFER_ALLOCATOR;
}

/**
* Creates a new thread factory for the netty event loop group but with daemon threads enabled.
*
* @return the newly constructed thread factory for event loops.
*/
private static @NonNull ThreadFactory createEventLoopThreadFactory() {
// same as MultithreadEventLoopGroup.newDefaultThreadFactory() but with daemon threads enabled
return new DefaultThreadFactory(MultithreadEventLoopGroup.class, true, Thread.MAX_PRIORITY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2019-2024 CloudNetService team & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package eu.cloudnetservice.wrapper.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Objects;
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Thread that executes the main method of the wrapped application and stays alive until the application exits, either
* normally or by throwing an exception.
*
* @since 4.0
*/
final class ApplicationThread extends Thread {

private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationThread.class);

private static final int LOGGED_ERROR_EXIST_STATUS = -1;
private static final int UNLOGGED_ERROR_EXIST_STATUS = -2;

private final Method mainMethod;
private final String[] mainArgs;

/**
* Constructs and setups the application thread for execution.
*
* @param mainMethod the main method to use for running the wrapped application.
* @param mainArgs the arguments to pass to the wrapped main method.
* @throws NullPointerException if the given main method or main args collection is null.
*/
public ApplicationThread(@NonNull Method mainMethod, @NonNull Collection<String> mainArgs) {
this.mainMethod = mainMethod;
this.mainArgs = mainArgs.toArray(String[]::new);

// explicitly disable daemon mode for the thread to ensure it keeps the wrapper
// alive until the wrapped application terminates in any way
super.setDaemon(false);
super.setName("Application-Thread");
super.setPriority(Thread.NORM_PRIORITY + 1);
}

/**
* {@inheritDoc}
*/
@Override
public void run() {
try {
this.mainMethod.invoke(null, new Object[]{this.mainArgs});
LOGGER.info("Invocation of application main method {} completed successfully", this.mainMethod);
return;
} catch (InvocationTargetException exception) {
// application threw error during execution
var cause = exception.getCause();
var exceptionToHandle = Objects.requireNonNullElse(cause, exception);
LOGGER.error("Caught application exception while running {}", this.mainMethod, exceptionToHandle);
} catch (IllegalArgumentException | NullPointerException exception) {
// illegal invocation of the given main method due to argument type mismatch
LOGGER.error("[BUG] Unable to invoke main method {} of application: {}", this.mainMethod, exception.getMessage());
} catch (IllegalAccessException exception) {
// illegal access to the main method, possibly private or in a module
LOGGER.error(
"The main method {} of the application cannot be called because the access modifiers of method are too strict: {}",
this.mainMethod, exception.getMessage());
} catch (Exception exception) {
LOGGER.error("Caught unexpected exception while running {}", this.mainMethod, exception);
} catch (Throwable _) {
// assume the worst case situation if no other catch clause handled the exception yet
// immediately exit the vm without even trying to log something (logging might fail as well)
Runtime.getRuntime().halt(UNLOGGED_ERROR_EXIST_STATUS);
}

// fall-through case for handled exceptions
System.exit(LOGGED_ERROR_EXIST_STATUS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package eu.cloudnetservice.wrapper.impl;

import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import eu.cloudnetservice.driver.inject.InjectionLayer;
import eu.cloudnetservice.driver.registry.ServiceRegistry;
import eu.cloudnetservice.wrapper.transform.ClassTransformerRegistry;
Expand Down Expand Up @@ -51,10 +52,15 @@ public static void main(@NonNull String... args) throws Throwable {
var builder = bootInjectLayer.injector().createBindingBuilder();
bootInjectLayer.install(builder.bind(org.slf4j.Logger.class).qualifiedWithName("root").toInstance(rootLogger));
bootInjectLayer.install(builder.bind(Instant.class).qualifiedWithName("startInstant").toInstance(startInstant));

var threadFactory = new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("CloudNet-TaskScheduler-Thread-%d")
.build();
bootInjectLayer.install(builder
.bind(ScheduledExecutorService.class)
.qualifiedWithName("taskScheduler")
.toInstance(Executors.newScheduledThreadPool(2)));
.toInstance(Executors.newScheduledThreadPool(2, threadFactory)));

// bind the transformer registry here - we *could* provided it by constructing, but we don't
// want to expose the Instrumentation instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ private void startApplication(

// get the main method
var main = Class.forName(mainClass, true, loader);
var method = main.getMethod("main", String[].class);
var mainMethod = main.getMethod("main", String[].class);

// inform the user about the pre-start
Collection<String> arguments = new LinkedList<>(consoleArgs);
Expand All @@ -248,15 +248,8 @@ private void startApplication(
System.setProperty("java.class.path", this.appendAppFileToClassPath(appFile));

// start the application
var applicationThread = new Thread(() -> {
try {
LOGGER.info("Starting application using class {} (pre-main: {})", mainClass, premainClass);
// start the application
method.invoke(null, new Object[]{arguments.toArray(new String[0])});
} catch (Exception exception) {
LOGGER.error("Exception while starting application", exception);
}
}, "Application-Thread");
LOGGER.info("Starting wrapped application using {} (pre-main class: {})", mainMethod, premainClass);
var applicationThread = new ApplicationThread(mainMethod, arguments);
applicationThread.setContextClassLoader(loader);
applicationThread.start();

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ eu.cloudnetservice.wrapper.impl.transform.bukkit.FAWEConfigTransformer
eu.cloudnetservice.wrapper.impl.transform.bukkit.FAWEReflectionUtilsTransformer
eu.cloudnetservice.wrapper.impl.transform.bukkit.WorldEditJava8DetectorTransformer
eu.cloudnetservice.wrapper.impl.transform.bukkit.FAWEWorldEditDownloadURLTransformer
eu.cloudnetservice.wrapper.impl.transform.minestom.MinestomStopCleanlyTransformer
eu.cloudnetservice.wrapper.impl.transform.netty.OldEpollDisableTransformer
eu.cloudnetservice.wrapper.impl.transform.spark.OldAsyncProfilerDisableTransformer
eu.cloudnetservice.wrapper.impl.transform.guava.GuavaLittleEndianByteArrayTransformer
Expand Down
Loading