Problem
SequenceDiagramFactory iterates the live MutableList<UmlDirective> from configuration.sequenceDiagram.directives (wired in KensaLifecycleManager). create() walks it via umlDirectives.flatMap { it.asUml() } with no synchronization, while another thread structurally mutates the same list (add via participant()/title()/box(), clear via reset()).
Under JUnit parallel execution, concurrent invocations ending on the ForkJoinPool throw ConcurrentModificationException.
java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1050)
at dev.kensa.render.diagram.SequenceDiagramFactory.create(SequenceDiagramFactory.kt)
at dev.kensa.state.TestInvocationFactory.create(TestInvocationFactory.kt:39)
at dev.kensa.context.KensaLifecycleManager.endInvocation(KensaLifecycleManager.kt:61)
at dev.kensa.junit.KensaExtension.afterTestExecution(KensaExtension.kt:46)
A defensive toList() inside create() does not fix it — the copy itself races. The lock must live with the data.
Fix
- Guard all directive mutations in
SequenceDiagramConfiguration with a lock; add a synchronized directivesSnapshot().
SequenceDiagramFactory consumes a () -> List<UmlDirective> provider returning that snapshot, so create() never iterates live state.
Regression test reproduces the CME reliably (4000+ occurrences) pre-fix and passes post-fix.
Problem
SequenceDiagramFactoryiterates the liveMutableList<UmlDirective>fromconfiguration.sequenceDiagram.directives(wired inKensaLifecycleManager).create()walks it viaumlDirectives.flatMap { it.asUml() }with no synchronization, while another thread structurally mutates the same list (addviaparticipant()/title()/box(),clearviareset()).Under JUnit parallel execution, concurrent invocations ending on the
ForkJoinPoolthrowConcurrentModificationException.A defensive
toList()insidecreate()does not fix it — the copy itself races. The lock must live with the data.Fix
SequenceDiagramConfigurationwith a lock; add a synchronizeddirectivesSnapshot().SequenceDiagramFactoryconsumes a() -> List<UmlDirective>provider returning that snapshot, socreate()never iterates live state.Regression test reproduces the CME reliably (4000+ occurrences) pre-fix and passes post-fix.