mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Resolve memory usage issues related to Netty 4.2 update (#5536)
* More proper workaround for direct connection using Netty 4.2 * Add check for outdated Velocity * BungeeCord support for Netty 4.2 * Use pooled allocator * Small cleanup: only one allocator system property check, pin comment to static commit * Set allocator type on Standalone using netty system property * Don't mess with allocator, both BungeeCord and Velocity set it globally * Temporarily remove io_uring support In order to support io_uring transport after the 4.2 update, we'd need to separately handle 4.1 and 4.2 event loop group creation * Add compileOnly netty io_uring dependency for Geyser-BungeeCord * Exclude old incubator io_uring dependency on geyser-standalone --------- Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,8 @@ plugins {
|
||||
dependencies {
|
||||
api(projects.core)
|
||||
|
||||
compileOnly(libs.netty.transport.native.io.uring)
|
||||
|
||||
implementation(libs.cloud.bungee)
|
||||
implementation(libs.adventure.text.serializer.bungeecord)
|
||||
compileOnlyApi(libs.bungeecord.proxy)
|
||||
|
||||
@@ -26,12 +26,28 @@
|
||||
package org.geysermc.geyser.platform.bungeecord;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.IoEventLoop;
|
||||
import io.netty.channel.IoEventLoopGroup;
|
||||
import io.netty.channel.IoHandler;
|
||||
import io.netty.channel.IoHandlerFactory;
|
||||
import io.netty.channel.MultiThreadIoEventLoopGroup;
|
||||
import io.netty.channel.SingleThreadIoEventLoop;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollIoHandler;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalIoHandler;
|
||||
import io.netty.channel.nio.NioIoHandler;
|
||||
import io.netty.channel.uring.IoUring;
|
||||
import io.netty.channel.uring.IoUringIoHandler;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import io.netty.util.concurrent.ThreadAwareExecutor;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.event.ProxyReloadEvent;
|
||||
@@ -43,12 +59,15 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.network.netty.GeyserInjector;
|
||||
import org.geysermc.geyser.network.netty.IoHandlerWrapper;
|
||||
import org.geysermc.geyser.network.netty.LocalServerChannelWrapper;
|
||||
import org.geysermc.geyser.network.netty.LocalSession;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
public class GeyserBungeeInjector extends GeyserInjector implements Listener {
|
||||
private final Plugin plugin;
|
||||
@@ -74,6 +93,13 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
|
||||
"Please reach out to us on our Discord at https://discord.gg/GeyserMC so we can hear feedback on your setup.");
|
||||
}
|
||||
|
||||
try {
|
||||
Class.forName("io.netty.channel.MultiThreadIoEventLoopGroup");
|
||||
} catch (ClassNotFoundException e) {
|
||||
bootstrap.getGeyserLogger().error("use-direct-connection disabled as BungeeCord is not up-to-date.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
try {
|
||||
ProxyServer.class.getMethod("unsafe");
|
||||
@@ -85,22 +111,39 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
|
||||
|
||||
Class<? extends ProxyServer> proxyClass = proxy.getClass();
|
||||
// Using the specified EventLoop is required, or else an error will be thrown
|
||||
EventLoopGroup bossGroup;
|
||||
EventLoopGroup workerGroup;
|
||||
MultiThreadIoEventLoopGroup workerGroup;
|
||||
try {
|
||||
EventLoopGroup eventLoops = (EventLoopGroup) proxyClass.getField("eventLoops").get(proxy);
|
||||
// Netty redirects ServerBootstrap#group(EventLoopGroup) to #group(EventLoopGroup, EventLoopGroup) and uses the same event loop for both.
|
||||
bossGroup = eventLoops;
|
||||
workerGroup = eventLoops;
|
||||
workerGroup = (MultiThreadIoEventLoopGroup) proxyClass.getField("eventLoops").get(proxy);
|
||||
bootstrap.getGeyserLogger().debug("BungeeCord event loop style detected.");
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Waterfall uses two separate event loops
|
||||
// https://github.com/PaperMC/Waterfall/blob/fea7ec356dba6c6ac28819ff11be604af6eb484e/BungeeCord-Patches/0022-Use-a-worker-and-a-boss-event-loop-group.patch
|
||||
bossGroup = (EventLoopGroup) proxyClass.getField("bossEventLoopGroup").get(proxy);
|
||||
workerGroup = (EventLoopGroup) proxyClass.getField("workerEventLoopGroup").get(proxy);
|
||||
workerGroup = (MultiThreadIoEventLoopGroup) proxyClass.getField("workerEventLoopGroup").get(proxy);
|
||||
bootstrap.getGeyserLogger().debug("Waterfall event loop style detected.");
|
||||
}
|
||||
|
||||
final IoEventLoopGroup finalWorkerGroup = workerGroup;
|
||||
var factory = LocalIoHandler.newFactory();
|
||||
var nativeFactory = getNativeHandlerFactory();
|
||||
var wrapperFactory = new IoHandlerFactory() {
|
||||
@Override
|
||||
public IoHandler newHandler(ThreadAwareExecutor ioExecutor) {
|
||||
return new IoHandlerWrapper(factory.newHandler(ioExecutor), nativeFactory.newHandler(ioExecutor));
|
||||
}
|
||||
};
|
||||
|
||||
EventLoopGroup wrapperGroup = new MultiThreadIoEventLoopGroup(factory) {
|
||||
@Override
|
||||
protected ThreadFactory newDefaultThreadFactory() {
|
||||
return new DefaultThreadFactory("Geyser Backend Worker Group", Thread.MAX_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IoEventLoop newChild(Executor executor, IoHandlerFactory ioHandlerFactory, Object... args) {
|
||||
return new SingleThreadIoEventLoop(finalWorkerGroup, executor, wrapperFactory);
|
||||
}
|
||||
};
|
||||
|
||||
// Is currently just AttributeKey.valueOf("ListerInfo") but we might as well copy the value itself.
|
||||
AttributeKey<ListenerInfo> listener = PipelineUtils.LISTENER;
|
||||
listenerInfo = new ListenerInfo(
|
||||
@@ -157,7 +200,7 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
|
||||
}
|
||||
})
|
||||
.childAttr(listener, listenerInfo)
|
||||
.group(bossGroup, workerGroup)
|
||||
.group(new MultiThreadIoEventLoopGroup(LocalIoHandler.newFactory()), wrapperGroup)
|
||||
.localAddress(LocalAddress.ANY))
|
||||
.bind()
|
||||
.syncUninterruptibly();
|
||||
@@ -202,4 +245,16 @@ public class GeyserBungeeInjector extends GeyserInjector implements Listener {
|
||||
initializeLocalChannel(GeyserImpl.getInstance().getBootstrap());
|
||||
}
|
||||
}
|
||||
|
||||
private static IoHandlerFactory getNativeHandlerFactory() {
|
||||
// Match whatever settings the Bungee proxy is using
|
||||
// https://github.com/SpigotMC/BungeeCord/blob/617c2728a25347487eee4e8649d52fe57f1ff6e2/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java#L139-L162
|
||||
if (Boolean.parseBoolean(System.getProperty("bungee.io_uring", "false")) && IoUring.isAvailable()) {
|
||||
return IoUringIoHandler.newFactory();
|
||||
}
|
||||
if (Boolean.parseBoolean(System.getProperty("bungee.epoll", "true")) && Epoll.isAvailable()) {
|
||||
return EpollIoHandler.newFactory();
|
||||
}
|
||||
return NioIoHandler.newFactory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.platform.bungeecord;
|
||||
|
||||
import io.netty.buffer.AdaptiveByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
@@ -82,9 +84,12 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
||||
public void onGeyserInitialize() {
|
||||
GeyserLocale.init(this);
|
||||
|
||||
// TODO remove when this isn't an issue anymore
|
||||
boolean adaptiveAllocatorUsed = System.getProperty("io.netty.allocator.type") == null && ByteBufAllocator.DEFAULT instanceof AdaptiveByteBufAllocator;
|
||||
|
||||
try {
|
||||
List<Integer> supportedProtocols = ProtocolConstants.SUPPORTED_VERSION_IDS;
|
||||
if (!supportedProtocols.contains(GameProtocol.getJavaProtocolVersion())) {
|
||||
if (!supportedProtocols.contains(GameProtocol.getJavaProtocolVersion()) || adaptiveAllocatorUsed) {
|
||||
geyserLogger.error(" / \\");
|
||||
geyserLogger.error(" / \\");
|
||||
geyserLogger.error(" / | \\");
|
||||
|
||||
@@ -32,6 +32,11 @@ tasks.named<Jar>("jar") {
|
||||
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
archiveBaseName.set("Geyser-Standalone")
|
||||
|
||||
// temporary measure - incubator's io_uring is not compatible with 4.2.1
|
||||
dependencies {
|
||||
exclude(dependency("io.netty.incubator:.*"))
|
||||
}
|
||||
|
||||
transform(Log4j2PluginsCacheFileTransformer())
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); // Can eat performance
|
||||
}
|
||||
|
||||
// Restore allocator used before Netty 4.2 due to oom issues with the adaptive allocator
|
||||
if (System.getProperty("io.netty.allocator.type") == null) {
|
||||
System.setProperty("io.netty.allocator.type", "pooled");
|
||||
}
|
||||
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
GeyserStandaloneLogger.setupStreams();
|
||||
|
||||
|
||||
@@ -27,15 +27,29 @@ package org.geysermc.geyser.platform.velocity;
|
||||
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.IoEventLoop;
|
||||
import io.netty.channel.IoHandlerFactory;
|
||||
import io.netty.channel.MultiThreadIoEventLoopGroup;
|
||||
import io.netty.channel.WriteBufferWaterMark;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalIoHandler;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.network.netty.GeyserInjector;
|
||||
import org.geysermc.geyser.network.netty.LocalServerChannelWrapper;
|
||||
import org.geysermc.geyser.network.netty.WatchedSingleThreadIoEventLoop;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class GeyserVelocityInjector extends GeyserInjector {
|
||||
@@ -48,11 +62,11 @@ public class GeyserVelocityInjector extends GeyserInjector {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void initializeLocalChannel0(GeyserBootstrap bootstrap) throws Exception {
|
||||
// TEMPORARY until Netty 4.2 is implemented with these changes in mind.
|
||||
try {
|
||||
Class.forName("io.netty.channel.MultiThreadIoEventLoopGroup");
|
||||
} catch (ClassNotFoundException e) {
|
||||
bootstrap.getGeyserLogger().error("use-direct-connection disabled as Velocity is not up-to-date.");
|
||||
return;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
Field cm = proxy.getClass().getDeclaredField("cm");
|
||||
@@ -70,34 +84,44 @@ public class GeyserVelocityInjector extends GeyserInjector {
|
||||
serverWriteMarkField.setAccessible(true);
|
||||
WriteBufferWaterMark serverWriteMark = (WriteBufferWaterMark) serverWriteMarkField.get(null);
|
||||
|
||||
EventLoopGroup bossGroup = (EventLoopGroup) connectionManagerClass.getMethod("getBossGroup").invoke(connectionManager);
|
||||
|
||||
Field workerGroupField = connectionManagerClass.getDeclaredField("workerGroup");
|
||||
workerGroupField.setAccessible(true);
|
||||
EventLoopGroup workerGroup = (EventLoopGroup) workerGroupField.get(connectionManager);
|
||||
|
||||
EventLoopGroup wrapperGroup = new MultiThreadIoEventLoopGroup(LocalIoHandler.newFactory()) {
|
||||
@Override
|
||||
protected ThreadFactory newDefaultThreadFactory() {
|
||||
return new DefaultThreadFactory("Geyser Backend Worker Group", Thread.MAX_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IoEventLoop newChild(Executor executor, IoHandlerFactory ioHandlerFactory, Object... args) {
|
||||
return new WatchedSingleThreadIoEventLoop(workerGroup, this, executor, ioHandlerFactory);
|
||||
}
|
||||
};
|
||||
|
||||
// This method is what initializes the connection in Java Edition, after Netty is all set.
|
||||
Method initChannel = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class);
|
||||
initChannel.setAccessible(true);
|
||||
|
||||
ChannelFuture channelFuture = (new ServerBootstrap()
|
||||
.channel(LocalServerChannelWrapper.class)
|
||||
.childHandler(new ChannelInitializer<>() {
|
||||
@Override
|
||||
protected void initChannel(@NonNull Channel ch) throws Exception {
|
||||
initChannel.invoke(channelInitializer, ch);
|
||||
.channel(LocalServerChannelWrapper.class)
|
||||
.childHandler(new ChannelInitializer<>() {
|
||||
@Override
|
||||
protected void initChannel(@NonNull Channel ch) throws Exception {
|
||||
initChannel.invoke(channelInitializer, ch);
|
||||
|
||||
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserVelocityCompressionDisabler.ENABLED) {
|
||||
ch.pipeline().addAfter("minecraft-encoder", "geyser-compression-disabler",
|
||||
new GeyserVelocityCompressionDisabler());
|
||||
}
|
||||
if (bootstrap.getGeyserConfig().isDisableCompression() && GeyserVelocityCompressionDisabler.ENABLED) {
|
||||
ch.pipeline().addAfter("minecraft-encoder", "geyser-compression-disabler",
|
||||
new GeyserVelocityCompressionDisabler());
|
||||
}
|
||||
})
|
||||
.group(bossGroup, workerGroup) // Cannot be DefaultEventLoopGroup
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, serverWriteMark) // Required or else rare network freezes can occur
|
||||
.localAddress(LocalAddress.ANY))
|
||||
.bind()
|
||||
.syncUninterruptibly();
|
||||
}
|
||||
})
|
||||
.group(new MultiThreadIoEventLoopGroup(LocalIoHandler.newFactory()), wrapperGroup) // Cannot be DefaultEventLoopGroup
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, serverWriteMark) // Required or else rare network freezes can occur
|
||||
.localAddress(LocalAddress.ANY))
|
||||
.bind()
|
||||
.syncUninterruptibly();
|
||||
|
||||
this.localChannel = channelFuture;
|
||||
this.serverSocketAddress = channelFuture.channel().localAddress();
|
||||
|
||||
@@ -36,6 +36,8 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import io.netty.buffer.AdaptiveByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@@ -91,7 +93,10 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
||||
public void onGeyserInitialize() {
|
||||
GeyserLocale.init(this);
|
||||
|
||||
if (!ProtocolVersion.isSupported(GameProtocol.getJavaProtocolVersion())) {
|
||||
// TODO remove when this isn't an issue anymore
|
||||
boolean adaptiveAllocatorUsed = System.getProperty("io.netty.allocator.type") == null && ByteBufAllocator.DEFAULT instanceof AdaptiveByteBufAllocator;
|
||||
|
||||
if (!ProtocolVersion.isSupported(GameProtocol.getJavaProtocolVersion()) || adaptiveAllocatorUsed) {
|
||||
geyserLogger.error(" / \\");
|
||||
geyserLogger.error(" / \\");
|
||||
geyserLogger.error(" / | \\");
|
||||
|
||||
@@ -43,11 +43,11 @@ dependencies {
|
||||
// Network dependencies we are updating ourselves
|
||||
api(libs.netty.handler)
|
||||
|
||||
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } }
|
||||
api(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } }
|
||||
implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } }
|
||||
implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } }
|
||||
implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
|
||||
implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
|
||||
//api(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } }
|
||||
//implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } }
|
||||
|
||||
// Adventure text serialization
|
||||
api(libs.bundles.adventure)
|
||||
|
||||
@@ -38,9 +38,6 @@ import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.incubator.channel.uring.IOUring;
|
||||
import io.netty.incubator.channel.uring.IOUringDatagramChannel;
|
||||
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import lombok.Getter;
|
||||
import net.jodah.expiringmap.ExpirationPolicy;
|
||||
@@ -434,11 +431,12 @@ public final class GeyserServer {
|
||||
}
|
||||
|
||||
private static Transport compatibleTransport() {
|
||||
if (isClassAvailable("io.netty.incubator.channel.uring.IOUring")
|
||||
&& IOUring.isAvailable()
|
||||
&& Boolean.parseBoolean(System.getProperty("Geyser.io_uring"))) {
|
||||
return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
|
||||
}
|
||||
// FIXME supporting io_uring post 4.2 requires more changes
|
||||
// if (isClassAvailable("io.netty.incubator.channel.uring.IOUring")
|
||||
// && IOUring.isAvailable()
|
||||
// && Boolean.parseBoolean(System.getProperty("Geyser.io_uring"))) {
|
||||
// return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
|
||||
// }
|
||||
|
||||
if (isClassAvailable("io.netty.channel.epoll.Epoll") && Epoll.isAvailable()) {
|
||||
return new Transport(EpollDatagramChannel.class, EpollEventLoopGroup::new);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.network.netty;
|
||||
|
||||
import io.netty.channel.IoHandle;
|
||||
import io.netty.channel.IoHandler;
|
||||
import io.netty.channel.IoHandlerContext;
|
||||
import io.netty.channel.IoRegistration;
|
||||
|
||||
public class IoHandlerWrapper implements IoHandler {
|
||||
private final IoHandler localHandler;
|
||||
private final IoHandler nativeHandler;
|
||||
private final IoHandlerContextWrapper contextWrapper = new IoHandlerContextWrapper();
|
||||
|
||||
public IoHandlerWrapper(IoHandler localHandler, IoHandler nativeHandler) {
|
||||
this.localHandler = localHandler;
|
||||
this.nativeHandler = nativeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
localHandler.initialize();
|
||||
nativeHandler.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int run(IoHandlerContext context) {
|
||||
contextWrapper.base = context;
|
||||
localHandler.run(contextWrapper);
|
||||
return nativeHandler.run(context);
|
||||
}
|
||||
|
||||
private static class IoHandlerContextWrapper implements IoHandlerContext {
|
||||
private IoHandlerContext base;
|
||||
|
||||
@Override
|
||||
public boolean canBlock() {
|
||||
// https://github.com/netty/netty/blob/7ac5e2ba20030caad0d0e648e86f31e9f0461105/transport/src/main/java/io/netty/channel/local/LocalIoHandler.java#L63
|
||||
// will block the other IoHandler.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long delayNanos(long currentTimeNanos) {
|
||||
return base.delayNanos(currentTimeNanos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long deadlineNanos() {
|
||||
return base.deadlineNanos();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareToDestroy() {
|
||||
localHandler.prepareToDestroy();
|
||||
nativeHandler.prepareToDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
localHandler.destroy();
|
||||
nativeHandler.destroy();
|
||||
}
|
||||
|
||||
private static final Class<? extends IoHandle> LOCAL_HANDLER_CLASS;
|
||||
|
||||
static {
|
||||
try {
|
||||
LOCAL_HANDLER_CLASS = (Class<? extends IoHandle>) Class.forName("io.netty.channel.local.LocalIoHandle");
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IoRegistration register(IoHandle handle) throws Exception {
|
||||
if (LOCAL_HANDLER_CLASS.isAssignableFrom(handle.getClass())) {
|
||||
return this.localHandler.register(handle);
|
||||
}
|
||||
return this.nativeHandler.register(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wakeup() {
|
||||
localHandler.wakeup();
|
||||
nativeHandler.wakeup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatible(Class<? extends IoHandle> handleType) {
|
||||
return localHandler.isCompatible(handleType) || nativeHandler.isCompatible(handleType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.network.netty;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.IoEventLoopGroup;
|
||||
import io.netty.channel.IoHandlerFactory;
|
||||
import io.netty.channel.SingleThreadIoEventLoop;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class WatchedSingleThreadIoEventLoop extends SingleThreadIoEventLoop {
|
||||
private final EventLoopGroup trueWorkerGroup;
|
||||
|
||||
public WatchedSingleThreadIoEventLoop(EventLoopGroup trueWorkerGroup, IoEventLoopGroup parent,
|
||||
Executor executor, IoHandlerFactory ioHandlerFactory) {
|
||||
super(parent, executor, ioHandlerFactory);
|
||||
this.trueWorkerGroup = trueWorkerGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public ChannelFuture register(Channel channel) {
|
||||
if (channel.getClass().getName().startsWith("org.geysermc.geyser")) {
|
||||
return super.register(channel);
|
||||
}
|
||||
// Starting with Netty 4.2, channels/event loops are very picky with what can be accepted for each.
|
||||
// For example, IoUringIoHandler (on a Linux machine, what Velocity's worker group will be)
|
||||
// will not accept LocalChannels on bootstrap creation in GeyserVelocityInjector.
|
||||
// And using a MultiThreadEventLoopGroup with LocalIoHandler will throw an error when trying to
|
||||
// connect to the backend server.
|
||||
// Inserting ourselves here allows our local channels to use the event loop made for LocalChannels,
|
||||
// while re-using the settings and style of Velocity.
|
||||
return this.trueWorkerGroup.register(channel);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,7 @@ erosion = "1.1-20240521.000109-3"
|
||||
events = "1.1-SNAPSHOT"
|
||||
jackson = "2.17.0"
|
||||
fastutil = "8.5.15-SNAPSHOT"
|
||||
netty = "4.1.107.Final"
|
||||
netty-io-uring = "0.0.25.Final-SNAPSHOT"
|
||||
netty = "4.2.1.Final"
|
||||
guava = "29.0-jre"
|
||||
gson = "2.3.1" # Provided by Spigot 1.8.8
|
||||
websocket = "1.5.1"
|
||||
@@ -88,7 +87,7 @@ netty-codec-haproxy = { group = "io.netty", name = "netty-codec-haproxy", versio
|
||||
netty-handler = { group = "io.netty", name = "netty-handler", version.ref = "netty" }
|
||||
netty-transport-native-epoll = { group = "io.netty", name = "netty-transport-native-epoll", version.ref = "netty" }
|
||||
netty-transport-native-kqueue = { group = "io.netty", name = "netty-transport-native-kqueue", version.ref = "netty" }
|
||||
netty-transport-native-io_uring = { group = "io.netty.incubator", name = "netty-incubator-transport-native-io_uring", version.ref = "netty-io-uring" }
|
||||
netty-transport-native-io_uring = { group = "io.netty", name = "netty-transport-native-io_uring", version.ref = "netty" }
|
||||
|
||||
log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" }
|
||||
log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" }
|
||||
|
||||
Reference in New Issue
Block a user