Skip to content

ComponentUtils.copyIcon may produce ConcurrentModificationException #5408

Description

@glebfox

Environment

Jmix version: 3.0
Source: forum

Bug Description

ComponentUtils.copyIcon copies all attributes, including vaadin-tooltip which is a separate component:

target.setTooltipText(source.getTooltip().getText());

AbstractIcon.getTooltip()Tooltip.forHasTooltip(source) lazily creates and attaches a
tooltip element to the slot of the source icon
on first call — mutating the StateNode of the
shared MenuConfig icon. When two UI threads build their menus concurrently, they mutate the
same StateNode in parallel → HashMap.computeIfAbsent in StateNode.getChangeTrackerCME.

Steps To Reproduce

Concurrently copy a single shared icon:

import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import io.jmix.flowui.kit.component.ComponentUtils;
import java.util.concurrent.*;

public class CopyIconRace {
    public static void main(String[] args) throws Exception {
        Icon shared = VaadinIcon.HOME.create();   // cold: tooltip not yet created
        // shared.getTooltip();                    // <-- uncommenting this (warm-up) makes the race disappear
        int threads = 16;
        ExecutorService pool = Executors.newFixedThreadPool(threads);
        CountDownLatch start = new CountDownLatch(1);
        for (int i = 0; i < threads; i++) {
            pool.submit(() -> {
                start.await();
                for (int j = 0; j < 500; j++) ComponentUtils.copyIcon(shared);
                return null;
            });
        }
        start.countDown();
        pool.shutdown();
        pool.awaitTermination(30, TimeUnit.SECONDS);
    }
}

Current Behavior

java.util.ConcurrentModificationException
	at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1229)
	at com.vaadin.flow.internal.StateNode.getChangeTracker(StateNode.java:966)
	at com.vaadin.flow.internal.nodefeature.NodeList.add(NodeList.java:240)
	at com.vaadin.flow.component.shared.SlotUtils.addToSlot(SlotUtils.java:130)
	at com.vaadin.flow.component.shared.Tooltip.forHasTooltip(Tooltip.java:164)
	at com.vaadin.flow.component.shared.HasTooltip.getTooltip(HasTooltip.java:64)
	at io.jmix.flowui.kit.component.ComponentUtils.copyAbstractIconAttributes(ComponentUtils.java:138)
	at io.jmix.flowui.kit.component.ComponentUtils.copyFontIcon(ComponentUtils.java:176)
	at io.jmix.flowui.kit.component.ComponentUtils.copyIcon(ComponentUtils.java:110)
	at io.jmix.flowui.menu.MenuItem.getIconComponent(MenuItem.java:256)
	at io.jmix.flowui.menu.ListMenuBuilder.setIcon(ListMenuBuilder.java:166)
	at io.jmix.flowui.menu.ListMenuBuilder.createViewMenuItem(ListMenuBuilder.java:216)
	at io.jmix.flowui.menu.provider.MenuConfigListMenuItemProvider.convertToMenuItems(MenuConfigListMenuItemProvider.java:52)
	at io.jmix.flowui.xml.layout.loader.component.ListMenuLoader.loadMenuConfig(ListMenuLoader.java:49)
	...

Expected Behavior

No exception

Metadata

Metadata

Assignees

Type

No fields configured for Bug.

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions