Repository navigation
Studies can hang at the end of a run since write() stopped advancing simtime #608
Description
While adding the end to end study test in #607 I noticed some studies never finish. It happens when the node that consumes the last value is a bit slower than the one producing it. I tracked it down to write() no longer moving simtime forward (#385 / #431).
How it happens
Take testsou/testZ.graphml. The plant (pmpymat.py) writes ym with delta=1, but since #431 its own simtime only moves when it reads u from the controller. When the controller reaches simtime 100 it leaves its loop and right away writes init_simtime_u ([0.0,0.0,0.0], timestamp 0) back to u. A string write only waits 2*delay (20 ms with delay = 0.01). If the plant hasn't read the last real u by then, it reads the reset value instead, its simtime stays at 99, and it sits in while concore.unchanged() forever.
Before #431, write() did simtime += delta, so the plant reached 100 by itself and the timing didn't matter.
Repro (Linux, posix build of testZ, plant delay changed in a copy of pmpymat.py):
| case | result |
|---|---|
unchanged (plant delay = 0.01) |
finishes in ~2s |
plant delay = 0.05 |
hangs |
plant delay = 0.05, with mod.simtime += delta put back in concore_base.write() |
finishes in ~6s |
On Windows the plain testZ build (--type windows) already hangs for me without changing anything, and testVZ (verilog controller, python plant) hung the same way under load while I was testing #607.
Where the pattern is used
Writing the init values back after the loop is in 37 study files, e.g. demo/cwrap.py, demo/pwrap.py, ratc/cwrap.py, ratc/pmvxmatcore.v, tools/cwrap.py, nintan/cpymax.py and most of testsou/. Slower bindings like MATLAB and Verilog make it more likely.
Fixing it without bringing back #385
#385 has a real point, tests/test_concore.py checks that two writes in the same step get the same timestamp, so just putting simtime += delta back would break that. Two options I can see:
- Advance on the next read.
write()stays as it is now (same payload, same timestamps), it just rememberssimtime + delta, and the nextread()movessimtimeup to that. Small change and no fixture changes. Downside: at the very end the slower node does one extra step with the reset value before it exits. - Advance right away, but stamp from the start of the step.
write()movessimtimeforward immediately, while the timestamp is computed from the time at the start of the step, so writes in the same step still share one timestamp. No extra step at the end, but every binding needs to keep that extra state, and it can get out of sync if a script setssimtimeby hand.
Either way it has to be the same in all bindings (Python, C++, Java, MATLAB/Octave, Verilog, Julia), and the write_zmq/..._without_mutation fixture would need a look.
@pradeeban which direction would you prefer? I'm happy to do the change across all the bindings, with an end to end test where the plant is slower so it fails today and passes after.
Activity
Sahil-u07 commentedon Oct 10, 2026
I'd like to work on this one, could you assign it to me? I can start with whichever option you prefer.