Skip to content

feat: separate outboundListening flag in ChannelDuplexHandlerPacketListener to avoid CPU overhead when no outbound listeners are registered #9

Description

@Hator13

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions