First initialized solution event shares the working solution with the consumer thread (ConcurrentModificationException)
I maintainer di solito rispondono entro 1 giorno
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
Describe the bug
The first initialized solution event gives the consumer the live working solution, not a planning clone. The consumer reads it on the consumer thread, while the solver thread continues to change the same object.
Mechanism (line numbers on main at 9de70fcf and on v2.7.0):
DefaultSolverJob.FirstInitializedSolutionPhaseLifecycleListener.phaseEnded(line 424) callsconsumerSupport.consumeFirstInitializedSolution(phaseScope.getWorkingSolution(), ...)without a clone. The best solution and final best solution events receive clones.ConsumerSupportruns the consumer asynchronously on its consumer thread.- On the solver thread,
AbstractPhase.phaseEndedfires the phase listeners (line 136). Then, ifassertPhaseScoreFromScratchis true (every asserted environment mode, including the defaultPHASE_ASSERT), it asserts the score from scratch on the same working solution (lines 137-144). - The assertion calls
uncorruptedScoreDirector.setWorkingSolution(workingSolution). This goes toBasicVariableState.resetWorkingSolution, which clears every@InverseRelationShadowVariablecollection and fills it again. - A consumer that iterates such a collection at that moment gets a
ConcurrentModificationException. A consumer that does not fail can read a half-filled collection.
The event fires only when another phase follows the construction heuristic. In our tests, local search did not change the consumed solution (more than 200,000 steps with NO_ASSERT). Only the assertion from scratch did.
Expected behavior
The solution in FirstInitializedSolutionEvent does not change while the consumer reads it, as with NewBestSolutionEvent and FinalBestSolutionEvent.
Actual behavior
With PHASE_ASSERT (the default) or FULL_ASSERT, the inverse relation shadow collections of the consumed solution are cleared and filled again while the consumer reads them. The consumer gets a ConcurrentModificationException.
In the Timefold service SDK, SolverWorker.notifyOnInit converts this solution to the model output on the consumer thread. A model whose ModelConvertor.toModelOutput reads an inverse relation shadow collection fails at random with Unable to create model output for dataset with id ... (caused by ConcurrentModificationException), and the dataset ends in SOLVING_FAILED. We saw this in about one of three runs of a packaged Quarkus integration test (test profile, FULL_ASSERT).
To Reproduce
Reproducer: a test in SolverManagerTest on branch larsbeck/timefold-solver:reproducer/first-initialized-solution-race (commit e449eb41). It uses the existing TestdataInverseRelation* test domain.
mvn -pl core -am test -Dtest='SolverManagerTest#firstInitializedSolutionDoesNotChangeWhileTheConsumerReadsIt' -Dsurefire.failIfNoSpecifiedTests=false
| Environment mode | Result |
|---|---|
PHASE_ASSERT (the default) |
Fails with ConcurrentModificationException (4 of 4 runs) |
NO_ASSERT |
Passes |
The consumer gets an iterator on every inverse relation collection, waits 500 ms, and then iterates them. The problem has 50 values and 2,000 entities, so that the assertion from scratch takes longer than the start of the consumer thread. With a small problem, the failure occurs only at random.
Environment
Timefold Solver Version or Git ref: 2.7.0 and main (9de70fcf)
Output of java -version: openjdk version "25.0.2" 2026-01-20 LTS (Temurin-25.0.2+10)
Output of uname -a or ver: Darwin 25.6.0 arm64
Additional information
- Possible fix (not tested): send a planning clone in the event, for example
phaseScope.getScoreDirector().cloneSolution(phaseScope.getWorkingSolution()), as the best solution events already do. - No merged pull request after the 2.7.0 release addresses this. #1583 ("clone the solution before sending it to the consumer") changed only the best solution events and was reverted in #1648.
- Lingua principale
- Java
- Stelle
- 1.8k
- Fork
- 238
- Merge medio
- 3g 1h
- PR unite (30g)
- 55
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TimefoldAI/timefold-solver
-
component/docs
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
TimefoldAI/timefold-solver#2671 ·
I maintainer di solito rispondono entro 1 giorno
-
process/needs triage
Difficoltà 5/5 Più di una settimana Idoneità per principianti 12/100
TimefoldAI/timefold-solver#2763 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Docs: Add keywords for search.Forse già presa @TomCools l’ha presa oggi. Apertacomponent/docs
TimefoldAI/timefold-solver#2762 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Swap move selector applies entity value range filtering to entity classes without entity-level rangesForse già presa @zepfred l’ha presa 1 giorno fa. Apertaprocess/needs triage
TimefoldAI/timefold-solver#2761 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
SDK: Waypoints class has not OpenAPI Spec annotations.Forse già presa @TomCools l’ha presa 1 giorno fa. Apertacomponent/service
TimefoldAI/timefold-solver#2759 · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di TimefoldAI/timefold-solver
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
apache/rocketmq-dashboard#6896 ·
I maintainer di solito rispondono entro 3 giorni
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
OpenIntegrationEngine/engine#496 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
SasanLabs/VulnerableApp#836 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement frontend
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
Albertinesilva/backend-engineering-journey-java-springboot#46 ·
-
Snyk: vulnerabilities in jackson-databind 2.21.5Forse già presa @harrywu23 l’ha presa oggi. Apertabug
Difficoltà 1/5 1-3 ore Idoneità per principianti 80/100
SOFTENG-310-Group-4/Project-A1-310-Group-4#115 ·
I maintainer di solito rispondono entro 1 giorno