Skip to content

Studies can hang at the end of a run since write() stopped advancing simtime #608

Open

Description

@Sahil-u07

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:

  1. Advance on the next read. write() stays as it is now (same payload, same timestamps), it just remembers simtime + delta, and the next read() moves simtime up 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.
  2. Advance right away, but stamp from the start of the step. write() moves simtime forward 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 sets simtime by 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

  1. Sahil-u07 commented on Oct 10, 2026

    @Sahil-u07
    ContributorAuthor

    I'd like to work on this one, could you assign it to me? I can start with whichever option you prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions