Skip to content

ConcurrentModificationException in View#drawSelf when children are modified during rendering #129

Description

@leniver

Is there an existing issue for this?

  • I have searched the existing issues.

Are you using the latest BlockUI Version?

  • I am running the latest beta/release version of BlockUI for my Minecraft version.
    I am also running the latest versions of other mods that are part of my problem.

Minecraft Version

1.21

BlockUI Version

1.0.211-1.21.1-snapshot

Current Behavior

View's render paths iterate the live children list with an unguarded for-each. If anything structurally modifies that list while a render pass is in flight, the client crashes with ConcurrentModificationException.

Crashed three times with the MineColonies Colony Map open (2026-08-19, then twice on 2026-09-04 two minutes apart). All three reports are identical.

Description: Rendering BO screen

java.util.ConcurrentModificationException: null
        at java.base/java.util.ArrayList$Itr.checkForComodification(Unknown Source)
        at java.base/java.util.ArrayList$Itr.next(Unknown Source)
        at com.ldtteam.blockui.views.View.drawSelf(View.java:86)
        at com.ldtteam.blockui.views.ZoomDragView.drawSelf(ZoomDragView.java:196)
        at com.ldtteam.blockui.Pane.draw(Pane.java:357)
        at com.ldtteam.blockui.views.View.drawSelf(View.java:90)
        at com.ldtteam.blockui.views.BOWindow.drawSelf(BOWindow.java:116)
        at com.ldtteam.blockui.Pane.draw(Pane.java:357)
        at com.ldtteam.blockui.BOScreen.render(BOScreen.java:106)
        at net.minecraft.client.gui.screens.Screen.renderWithTooltip(Screen.java:112)

The crash report's own detail block names the window:

-- BO screen rendering details --
Details:
        XML res loc: minecolonies:gui/map/windowcolonymap.xml
        Scaling mode (window render type): OVERSIZED_VANILLA

No other mod appears anywhere in the failing stack, it is entirely blockui -> vanilla Screen -> NeoForge ClientHooks.

Analysis

View.java on version/main has seven loops over children, of which exactly one is guarded:

line method guarded
86 drawSelf no
104 drawHidden no
127 drawSelfLast no
153 findPaneByID no
174 findPaneByType no
190 setWindow no
260 onUpdate yesnew ArrayList<>(children)

Line 86 is the loop in the stack trace:

public void drawSelf(final BOGuiGraphics target, final double mx, final double my)
{
    ...
    for (final Pane child : children)     // line 86
    {
        if (childIsVisible(child)) { child.draw(target, drawX, drawY); }
        else                       { child.drawHidden(); }
    }

On the consumer side, MineColonies' WindowColonyMap adds and removes child panes on its dragView as citizens move in and out of the visible area (12+ mutation sites, e.g. dragView.addChild(citizenView) and dragView.removeChild(citizens.get(data))), so in an active colony the child list changes constantly.

What I could not determine: which thread mutates the list during the render pass. WindowColonyMap's mutations all live under onUpdate(), which BlockUI drives from BOScreen#tick(), a different phase from BOScreen#render() and WindowColonyMap has no draw override. So the writer is likely off the render thread. Either way the render pass should not be able to die because of it, which is what the existing guard on line 260 already acknowledges.

Expected Behavior

A child pane being added or removed while a render pass is in flight should not crash the client.
View’s draw loops should be robust to concurrent modification, the same way View#onUpdate already is.

Reproduction Steps

  1. Join a colony with a good number of citizens moving around.
  2. Open the MineColonies Colony Map and leave it open.
  3. Pan/zoom while citizens walk in and out of the visible area.
  4. The client crashes with the ConcurrentModificationException above.

Intermittent, for me twice within two minutes on one occasion, and once several weeks earlier, which is consistent with a render update race rather than a deterministic code path.

Logs

https://pastebin.com/pUm2ghyE

Anything else?

Environment: MineColonies 1.1.1319-1.21.1, Structurize 1.0.830-1.21.1, NeoForge 21.1.233, Minecraft 1.21.1, Linux amd64, Java 21.0.12 (Temurin), multiplayer, GUI scale 2.

Suggested fix: apply the same defensive copy already used by onUpdate to the three draw methods (drawSelf, drawHidden, drawSelfLast).

If the per-frame allocation is a concern on large views (the colony map holds hundreds of child panes), alternatives would be index based iteration or making children a CopyOnWriteArrayList, reads are per-frame and writes are rare, which suits COW, though it would need a check of the reverse listIterator usage at line 423.

Footer


Viewers

  • Add a thumbs-up to the bug report if you are also affected. This helps the bug report become more visible to the team and doesn't clutter the comments.
  • Add a comment if you have any insights or background information that isn't already part of the conversation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions