Skip to content

Fix #20: end the Fabric networking session when a dummy leaves - #22

Closed
Bafy78 wants to merge 1 commit into
misode:mainfrom
the-great-chicken:fix/dummy-leave-fabric-session
Closed

Bafy78 wants to merge 1 commit into
misode:mainfrom
the-great-chicken:fix/dummy-leave-fabric-session

Conversation

@Bafy78

@Bafy78 Bafy78 commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Fixes #20.

Fabric API tracks a ServerPlayNetworkAddon for every ServerGamePacketListenerImpl in the static ServerNetworkingImpl.PLAY registry and only unregisters it from Connection.channelInactive() or from the onDisconnect call inside Connection.handleDisconnection(). A dummy connection has no channel, so neither path ever runs, and Dummy.leave() calls the listener's onDisconnect() directly. The player left the world, but the static registry kept the addon, the listener, the DummyClientConnection and everything it had queued reachable, and every spawn/leave cycle stacked on the previous one.

leave() now ends the Fabric session right before onDisconnect(), the order Fabric itself uses for real connections. handleDisconnect() is idempotent and also fires ServerPlayConnectionEvents.DISCONNECT, which mods listening to it never got for dummies.

PacketListenerExtensions is Fabric's implementation interface (its mixin makes every ServerGamePacketListenerImpl implement it), not public API. The alternative Fabric intends for fake players is a listener implementing UntrackedPacketListener (also impl), which would need a @WrapOperation on the listener construction in PlayerList.placeNewPlayer to give dummies their own listener class. I went with the three-line version; happy to switch if you prefer the marker.

Testing

Minecraft 26.3, Fabric API 0.161.0+26.3, dedicated server, 3 GB heap, 300 armor stands teleported every tick, 20 dummies spawned, kept online 30 s, then dummy leave, twice. jcmd GC.run + GC.class_histogram, counting ServerPlayNetworkAddon / ServerGamePacketListenerImpl / DummyClientConnection:

30 s after all dummies left cycle 1 cycle 2 post-full-GC heap
2.7.0 20 / 20 / 20 40 / 40 / 40 322 MB, then 419 MB (baseline 221 MB)
this PR 0 / 0 / 0 0 / 0 / 0 232 MB, then 229 MB

The departed dummies are still reachable 3 s after leaving and gone by the 30 s check; something vanilla-side holds them briefly, it does not accumulate. @a is empty after each leave in both builds, so the vanilla removal was never the problem.

Fabric only untracks a listener's network addon from Connection.channelInactive or handleDisconnection, which a channel-less dummy never reaches, so leave() now calls handleDisconnect() itself before onDisconnect.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@misode misode closed this Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dummy leave never releases the dummy: Fabric's networking registry keeps the listener and connection alive

2 participants