promotional bannermobile promotional banner
premium banner
Fixes a long-standing ConcurrentModificationException crash/disconnect in Weather2 caused by unsafe multi-threaded NBT packet encoding.

Description

Weather2 Concurrent Packet Fix

A lightweight Mixin patch that fixes a long-standing bug in Weather2, where the game client gets disconnected with the following error:

Failed to encode packet 'clientbound/minecraft:custom_payload'
Caused by: java.lang.RuntimeException: Failed encoding custom payload weather2:nbt_client
Caused by: java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode
    at net.minecraft.nbt.CompoundTag.write

This bug has existed for a long time and has not been fixed upstream. This mod serves as a drop-in fix until Weather2 is officially patched.


Root Cause

Weather2 encodes its nbt_client packet from Netty's I/O thread while the game thread simultaneously modifies the same CompoundTag's internal HashMap. Java's HashMap is not thread-safe — concurrent iteration and modification throws ConcurrentModificationException, which Minecraft catches and turns into a client disconnect.

Fix

This mod injects into CompoundTag.write() via Mixin. Before iterating the tag's entries for serialisation, it retries snapshotting the internal map until no concurrent modification occurs:

@Inject(method = "write", at = @At("HEAD"), cancellable = true)
private void safeWrite(DataOutput output, CallbackInfo ci) throws IOException {
    // Weather2 modifies tags without any lock, so synchronized(this) is insufficient.
    // Retry until a clean snapshot is obtained.
    List<Map.Entry<String, Tag>> entries = null;
    int attempts = 0;
    while (entries == null) {
        try {
            entries = new ArrayList<>(this.tags.entrySet());
        } catch (ConcurrentModificationException e) {
            if (++attempts >= MAX_RETRIES) {
                // Write empty tag to prevent disconnect as last resort
                output.writeByte(0);
                ci.cancel();
                return;
            }
        }
    }
    for (Map.Entry<String, Tag> e : entries) { ... }
    ci.cancel();
}

A retry cap of 100 is enforced — if exceeded, an empty tag is written, and a warning is logged to prevent an infinite loop. In practice, the snapshot succeeds within 1–3 attempts.

Compatibility

  • Does not modify Weather2 — may works with any version of Weather2
  • The patch applies to net.minecraft.nbt.CompoundTag, a vanilla class — any mod that writes a CompoundTag from a non-game thread benefits from this fix
  • No config required — install and forget

Supported Versions

File Loader Minecraft
weather2patch-x.x.x-neoforge-1.21.jar NeoForge 1.21.1, 1.21
weather2patch-x.x.x-neoforge-1.20.1.jar NeoForge 1.20.1
weather2patch-x.x.x-forge-1.20.1.jar Forge 1.20.1

Requirements

  • Weather2 + CoroUtil (this mod is only useful alongside Weather2, but has no hard dependency)

Notes

  • This mod patches at the CompoundTag level, not Weather2 specifically. If Weather2 is ever fixed upstream, this mod becomes a no-op (the injection still runs, but the behaviour is identical to vanilla since the fix is purely additive thread-safety).
  • On first activation, the following message is printed to console: [weather2patch] CompoundTag thread-safety patch is active.
  • Tested on NeoForge 21.1.220 + Weather2 2.8.6 + CoroUtil 1.3.8 at accelerated tickrate (1000 ticks/s) for ~20 minutes with zero disconnects.
  • I failed to replicate the issue on Forge 47.4.20 - 1.20.1, but I'm going to release a version for it anyway, hope it works 😅 (Updated to the functional version weather2patch-1.1.2-forge-1.20.1.jar).
  • Please avoid using the alpha version, as it may not work properly.
  • I'm not planning to regularly maintain or add support for other versions of this mod, as it is only a temporary fix for a modpack I am working on. I might change my mind if enough people ask me, though. Also, I'll archive this mod once the Weather2 issue is resolved in the upstream version.