Repository navigation
Batch reworking and related issues fixing - #1252
AlexisDrogoul wants to merge 15 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Worker lifecycle races and stop-condition failures can hang batches or leak threads, while thread-count APIs become inaccurate.
3 open findings
What changed in this PR
Reworks batch execution so simulations run autonomously and stop independently.
Changes:
- Adds autonomous simulation-runner APIs and lifecycle handling.
- Replaces batch stepping loops with queued completion processing.
- Updates batch status reporting and reload view cleanup.
| File | Description |
|---|---|
BatchAgent.java |
Coordinates autonomous runs and completion collection. |
SimulationPopulation.java |
Delegates autonomous execution to the runner. |
SimulationRunner.java |
Implements autonomous worker-thread behavior. |
ISimulationRunner.java |
Defines the autonomous execution API. |
GeneralSynchronizer.java |
Silences expected interruption traces. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
Handle exceptions when checking agent status and set 'over' accordingly. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: AlexisDrogoul <579256+AlexisDrogoul@users.noreply.github.com>
|
@chapuisk @lesquoyb @ptaillandier @RoiArthurB I think it is really important that we tackle all the remaining issues on batch before releasing anything ... Can you try / test / improve this PR ? |
|
I have begun to test batch models, and the fixes in this PR seem to be ok. Can someone verify this ? I havent a large range of batch models myself... I will try to produce better "regression models" for batch. |
until handling, thread leaks, status bar and reload

Summary
Reworks how batch experiments execute simulations so that each simulation runs on its own thread, independently of the others, and is stopped by its own
until:condition. Finished simulations are collected and replaced immediately, and their runner threads terminate.Fixes #198, #282, #325, #358, #199. Implements #418.
Background / root causes
Analysis of the issues and the code:
BatchAgentstepped simulations itself (sim.step()in a loop on the experiment thread, then evaluateduntil), whilecreateSimulation(.., scheduleIt = true)also registered each simulation withSimulationRunner, which owns one worker thread per simulation. Soparallel:had no real effect and the batch loop was effectively sequential, with lock-step "step all, then check all" semantics ([batch] Implementing truly parallel simulation execution #418, and the cross-simulation coupling described in "until:" facet's condition isn't respected in batch experiment #198).SimulationRunner.remove()only dropped the map entry; the worker stayed blocked on the semaphore forever.BatchAgent.closeSimulationsdoes not dispose the population, and the threads were non-daemon. Threads and simulations therefore accumulated for the whole batch, and could keep the JVM alive (behaviour reported in [batch] experiment doesn't kill previous simulations when instanciating new ones in some models #282, and "stuck" runs withparallel: 2in "until:" facet's condition isn't respected in batch experiment #198).permanentsection (Reloading a batch experiment with apermanentsection does not close the previous displays #199).Changes
gama.api—SimulationRunner,ISimulationRunner,GeneralSynchronizerISimulationRunner.runAutonomously(agent, isOver, isPaused, onOver): lets an already-added simulation step freely on its own thread untilisOveris true, then callsonOver(from that thread) and terminates. The simulation is removed from the synchronized-step accounting (activeCount), so it is no longer waited on bystep().SimulationThreadclass:runnablesbefore starting and exit as soon as they are no longer registered;remove()interrupts an idle (waiting) thread so it can exit instead of blocking forever on the semaphore;isPausedevery 10 ms).step()purges dead simulations before computing the number of permits so the experiment never waits on a simulation that can no longer release one.GeneralSynchronizer.acquire(..): removedprintStackTrace()onInterruptedException(interrupting idle threads is now an expected, normal event; the interrupt flag is still restored).gama.core—SimulationPopulationrunAutonomously(...)delegating to the runner.gama.extension.batch—BatchAgentrunSimulations(pop, concurrency, factory, remaining, collector)replaces the two near-duplicate loops inlaunchSimulationsInParallelWithParametersSetsandlaunchSimulationsWithSingleParametersSet:concurrencysimulations running; each runs freely and evaluates theuntilcondition in its own scope after each of its own steps;finallyblock unschedules every remaining simulation, so no thread leaks even if the experiment is killed or interrupted.N simulations finished | R running | W waiting (using T threads).runNumberis incremented when a simulation actually starts (previously pre-incremented for the whole batch), which keeps the final "Batch over. X runs" message accurate.closeSimulationsnow also closes the simulation/experiment views when the experiment is being reloaded (getSpecies().isReloading()), sopermanentdisplays of the old experiment are closed before the new one opens (Reloading a batch experiment with apermanentsection does not close the previous displays #199). It does not do so on the normal reset between runs.Behaviour changes to be aware of
simulations collect each.cyclein apermanentchart (see "until:" facet's condition isn't respected in batch experiment #198) will therefore show differing values, which is expected.parallel: 1(or one core) only one simulation runs at a time, as before, but on its own thread.Testing
Not yet performed — to do before merging:
until: cycle = 10,repeat: 3and the 100000-agent /permanentchart variant; each simulation ends at exactly cycle 10, independent of the slowest.repeat: 100 parallel: 4) withkeep_simulationstrue and false: old simulations don't linger, batch terminates, experiment reflex fires at the end.experiment a type: batch until: cycle = 10 repeat: 100 parallel: 2 keep_simulations: false(from "until:" facet's condition isn't respected in batch experiment #198): no longer gets stuck.parallel: 1,parallel: 2, all threads, andpref_parallel_simulations_alltrue/false.OptimizationinExploration.gaml): fitness aggregation still correct (fitness collected inprocessFinishedSimulation/manageOutputAndCloseSimulation).permanentsection does not close the previous displays #199: reloadexhaustive_exploration(usespermanent): old display closed and new one opened (a comment on Reloading a batch experiment with apermanentsection does not close the previous displays #199 reports the display not reopening — this PR closes the old views but I have not verified the reopening).Risks / review focus
SimulationRunner(thread wake-up viainterrupt()and thewaitingflag; races betweenremove(),runAutonomously()and thread start).parallel> 1.permanentoutputs read simulations from the experiment thread).