1
0
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:
chris
2025-05-22 17:43:02 +02:00
committed by GitHub
parent d0be6d9c48
commit 7c47459609
12 changed files with 321 additions and 45 deletions

View File

@@ -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)

View File

@@ -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();
}
}

View File

@@ -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(" / | \\");

View File

@@ -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())
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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(" / | \\");

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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" }