triplea-game / triplea

TripleA is a turn based strategy game and board game engine, similar to Axis & Allies or Risk.
https://triplea-game.org/
GNU General Public License v3.0
1.29k stars 382 forks source link

2.6.14752: HistorySynchronizer#lambda$startHistoryEvent$1:48 - java.util.ConcurrentModificationException #12623

Closed tripleabuilderbot closed 3 weeks ago

tripleabuilderbot commented 1 month ago

User Description

On turn 3 of Big World 2: Rise of Axis - normal gameplay

Map

big_world_2 / Big World 2 : Rise of the Axis

Log Message

Failed to map renderingData=[tiger owned by Germans] for event=Heavy Tanks Action: Germans has 1 tiger placed in Western Germany

TripleA Version

2.6.14752

Java Version

11.0.23

Operating System

Linux

Heap Size

1964M

Stack Trace

Exception: java.util.ConcurrentModificationException Failed to map renderingData=[tiger owned by Germans] for event=Heavy Tanks Action: Germans has 1 tiger placed in Western Germany
java.lang.Exception
    at games.strategy.engine.framework.HistorySynchronizer$1.lambda$startHistoryEvent$1(HistorySynchronizer.java:48)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Exception: java.util.ConcurrentModificationException 
java.lang.Exception
    at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1135)
    at games.strategy.triplea.image.UnitImageFactory.getIcon(UnitImageFactory.java:381)
    at games.strategy.triplea.ui.SimpleUnitPanel.addUnits(SimpleUnitPanel.java:152)
    at games.strategy.triplea.ui.SimpleUnitPanel.setUnitsFromCategories(SimpleUnitPanel.java:124)
    at games.strategy.triplea.ui.history.HistoryDetailsPanel.renderUnits(HistoryDetailsPanel.java:122)
    at games.strategy.triplea.ui.history.HistoryDetailsPanel.render(HistoryDetailsPanel.java:102)
    at games.strategy.triplea.ui.history.HistoryPanel.gotoNode(HistoryPanel.java:274)
    at games.strategy.triplea.ui.history.HistoryPanel.goToEnd(HistoryPanel.java:361)
    at games.strategy.engine.history.History.goToEnd(History.java:70)
    at games.strategy.engine.history.HistoryWriter.setRenderingData(HistoryWriter.java:183)
    at games.strategy.engine.framework.HistorySynchronizer$1.lambda$startHistoryEvent$1(HistorySynchronizer.java:45)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
asvitkine commented 1 month ago

Looks like UnitImageFactory is being used from multiple threads. If it's not supposed to, we can add assertions that we're on the EDT there to catch that. Or if it's supposed to be OK, we can make the collections there synchronized.

asvitkine commented 1 month ago

It seems that UnitImageFactory is being used from multiple threads already, e.g. via tile drawing code:

java.lang.IllegalStateException: null
    at com.google.common.base.Preconditions.checkState(Preconditions.java:499)
    at games.strategy.triplea.image.UnitImageFactory.getImage(UnitImageFactory.java:238)
    at games.strategy.triplea.ui.screen.UnitsDrawer.draw(UnitsDrawer.java:123)
    at games.strategy.triplea.ui.screen.Tile.draw(Tile.java:69)
    at games.strategy.triplea.ui.screen.Tile.drawImage(Tile.java:54)
    at games.strategy.triplea.ui.panels.map.MapPanel.lambda$paint$9(MapPanel.java:770)

So perhaps we should just make its collections synchronized.

For the case of getIcon(), I think the issue is clearCache() being called outside of EDT via:

data.addGameDataEventListener(
        GameDataEvent.TECH_ATTACHMENT_CHANGED, this::clearCachedUnitImages);

So that can be put on EDT, but that doesn't solve the issue of other functions being used outside of EDT...