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.getChangeTracker → CME.
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
Environment
Jmix version: 3.0
Source: forum
Bug Description
ComponentUtils.copyIconcopies all attributes, includingvaadin-tooltipwhich is a separate component:AbstractIcon.getTooltip()→Tooltip.forHasTooltip(source)lazily creates and attaches atooltip element to the slot of the source icon on first call — mutating the
StateNodeof theshared
MenuConfigicon. When two UI threads build their menus concurrently, they mutate thesame
StateNodein parallel →HashMap.computeIfAbsentinStateNode.getChangeTracker→CME.Steps To Reproduce
Concurrently copy a single shared icon:
Current Behavior
Expected Behavior
No exception