Problem
On a Paper 1.21 server with ~90 players, profiling with Spark revealed that
ChannelDuplexHandlerPacketListener.write() was generating ~435,212 CPU samples
on Netty I/O threads over 5 minutes, with an effective useful-work ratio of ~0.002%.
The root cause: PacketEvents.listening is set to true at startup and never
goes back to false. This means write() processes every single outbound packet
for every player, even when the plugin has no PacketEntity listeners registered
and has nothing to do with outbound traffic.
Proposed Fix
Introduce a separate outboundListening boolean field (defaulting to false)
alongside the existing listening field. The write() method would check
outboundListening first and return immediately if false, leaving the
channelRead() / inbound path completely untouched.
This way, plugins that don't use PacketEntities (outbound) pay zero cost on the
write path.
Workaround
We are currently shipping a patched JAR produced with ASM that injects this change
into ChannelDuplexHandlerPacketListener at the bytecode level. It works, but we'd
prefer to depend on the official artifact once this is fixed upstream.
Impact
Zero CPU overhead on the Netty write path for plugins that don't register outbound
listeners. No behavioral change for plugins that do.
Problem
On a Paper 1.21 server with ~90 players, profiling with Spark revealed that
ChannelDuplexHandlerPacketListener.write()was generating ~435,212 CPU sampleson Netty I/O threads over 5 minutes, with an effective useful-work ratio of ~0.002%.
The root cause:
PacketEvents.listeningis set totrueat startup and nevergoes back to
false. This meanswrite()processes every single outbound packetfor every player, even when the plugin has no PacketEntity listeners registered
and has nothing to do with outbound traffic.
Proposed Fix
Introduce a separate
outboundListeningboolean field (defaulting tofalse)alongside the existing
listeningfield. Thewrite()method would checkoutboundListeningfirst and return immediately iffalse, leaving thechannelRead()/ inbound path completely untouched.This way, plugins that don't use PacketEntities (outbound) pay zero cost on the
write path.
Workaround
We are currently shipping a patched JAR produced with ASM that injects this change
into
ChannelDuplexHandlerPacketListenerat the bytecode level. It works, but we'dprefer to depend on the official artifact once this is fixed upstream.
Impact
Zero CPU overhead on the Netty write path for plugins that don't register outbound
listeners. No behavioral change for plugins that do.