From 0e6ff4f10cdb1810ced9ab4afc34b363fedb991b Mon Sep 17 00:00:00 2001 From: syntron Date: Sun, 23 Nov 2025 13:25:50 +0100 Subject: [PATCH 1/7] [unittests] use new definitions / remove OMCSessionZMQ --- tests/test_ArrayDimension.py | 16 ++++----- tests/test_FMIRegression.py | 12 +++---- tests/test_ModelicaSystem.py | 19 +++++----- tests/test_ModelicaSystemDoE.py | 7 ++-- tests/test_OMCPath.py | 43 ++++++++-------------- tests/test_OMSessionCmd.py | 4 +-- tests/test_ZMQ.py | 63 ++++++++++++++++----------------- tests/test_docker.py | 24 +++++-------- 8 files changed, 82 insertions(+), 106 deletions(-) diff --git a/tests/test_ArrayDimension.py b/tests/test_ArrayDimension.py index 13b3c11b9..6e80d53f0 100644 --- a/tests/test_ArrayDimension.py +++ b/tests/test_ArrayDimension.py @@ -2,18 +2,18 @@ def test_ArrayDimension(tmp_path): - omc = OMPython.OMCSessionZMQ() + omcs = OMPython.OMCSessionLocal() - omc.sendExpression(f'cd("{tmp_path.as_posix()}")') + omcs.sendExpression(f'cd("{tmp_path.as_posix()}")') - omc.sendExpression('loadString("model A Integer x[5+1,1+6]; end A;")') - omc.sendExpression("getErrorString()") + omcs.sendExpression('loadString("model A Integer x[5+1,1+6]; end A;")') + omcs.sendExpression("getErrorString()") - result = omc.sendExpression("getComponents(A)") + result = omcs.sendExpression("getComponents(A)") assert result[0][-1] == (6, 7), "array dimension does not match" - omc.sendExpression('loadString("model A Integer y = 5; Integer x[y+1,1+9]; end A;")') - omc.sendExpression("getErrorString()") + omcs.sendExpression('loadString("model A Integer y = 5; Integer x[y+1,1+9]; end A;")') + omcs.sendExpression("getErrorString()") - result = omc.sendExpression("getComponents(A)") + result = omcs.sendExpression("getComponents(A)") assert result[-1][-1] == ('y+1', 10), "array dimension does not match" diff --git a/tests/test_FMIRegression.py b/tests/test_FMIRegression.py index b61b8d497..8a91c5143 100644 --- a/tests/test_FMIRegression.py +++ b/tests/test_FMIRegression.py @@ -7,21 +7,21 @@ def buildModelFMU(modelName): - omc = OMPython.OMCSessionZMQ() + omcs = OMPython.OMCSessionLocal() tempdir = pathlib.Path(tempfile.mkdtemp()) try: - omc.sendExpression(f'cd("{tempdir.as_posix()}")') + omcs.sendExpression(f'cd("{tempdir.as_posix()}")') - omc.sendExpression("loadModel(Modelica)") - omc.sendExpression("getErrorString()") + omcs.sendExpression("loadModel(Modelica)") + omcs.sendExpression("getErrorString()") fileNamePrefix = modelName.split(".")[-1] exp = f'buildModelFMU({modelName}, fileNamePrefix="{fileNamePrefix}")' - fmu = omc.sendExpression(exp) + fmu = omcs.sendExpression(exp) assert os.path.exists(fmu) finally: - del omc + del omcs shutil.rmtree(tempdir, ignore_errors=True) diff --git a/tests/test_ModelicaSystem.py b/tests/test_ModelicaSystem.py index dcc55d0ba..dd0321ec8 100644 --- a/tests/test_ModelicaSystem.py +++ b/tests/test_ModelicaSystem.py @@ -47,14 +47,15 @@ def worker(): ) mod.simulate() mod.convertMo2Fmu(fmuType="me") + for _ in range(10): worker() def test_setParameters(): - omc = OMPython.OMCSessionZMQ() - model_path_str = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels" - model_path = omc.omcpath(model_path_str) + omcs = OMPython.OMCSessionLocal() + model_path_str = omcs.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels" + model_path = omcs.omcpath(model_path_str) mod = OMPython.ModelicaSystem() mod.model( model_file=model_path / "BouncingBall.mo", @@ -87,9 +88,9 @@ def test_setParameters(): def test_setSimulationOptions(): - omc = OMPython.OMCSessionZMQ() - model_path_str = omc.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels" - model_path = omc.omcpath(model_path_str) + omcs = OMPython.OMCSessionLocal() + model_path_str = omcs.sendExpression("getInstallationDirectoryPath()") + "/share/doc/omc/testmodels" + model_path = omcs.omcpath(model_path_str) mod = OMPython.ModelicaSystem() mod.model( model_file=model_path / "BouncingBall.mo", @@ -155,11 +156,9 @@ def test_customBuildDirectory(tmp_path, model_firstorder): @skip_on_windows @skip_python_older_312 def test_getSolutions_docker(model_firstorder): - omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - omc = OMPython.OMCSessionZMQ(omc_process=omcp) - + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") mod = OMPython.ModelicaSystem( - session=omc.omc_process, + session=omcs, ) mod.model( model_file=model_firstorder, diff --git a/tests/test_ModelicaSystemDoE.py b/tests/test_ModelicaSystemDoE.py index 79c6e62d8..0e8d6caae 100644 --- a/tests/test_ModelicaSystemDoE.py +++ b/tests/test_ModelicaSystemDoE.py @@ -69,15 +69,14 @@ def test_ModelicaSystemDoE_local(tmp_path, model_doe, param_doe): @skip_on_windows @skip_python_older_312 def test_ModelicaSystemDoE_docker(tmp_path, model_doe, param_doe): - omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - omc = OMPython.OMCSessionZMQ(omc_process=omcp) - assert omc.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + assert omcs.sendExpression("getVersion()") == "OpenModelica 1.25.0" doe_mod = OMPython.ModelicaSystemDoE( model_file=model_doe, model_name="M", parameters=param_doe, - session=omcp, + session=omcs, simargs={"override": {'stopTime': 1.0}}, ) diff --git a/tests/test_OMCPath.py b/tests/test_OMCPath.py index b37e7c633..2ea8b8c8d 100644 --- a/tests/test_OMCPath.py +++ b/tests/test_OMCPath.py @@ -15,54 +15,41 @@ ) -def test_OMCPath_OMCSessionZMQ(): - om = OMPython.OMCSessionZMQ() - - _run_OMCPath_checks(om) - - del om - - def test_OMCPath_OMCProcessLocal(): - omp = OMPython.OMCSessionLocal() - om = OMPython.OMCSessionZMQ(omc_process=omp) + omcs = OMPython.OMCSessionLocal() - _run_OMCPath_checks(om) + _run_OMCPath_checks(omcs) - del om + del omcs @skip_on_windows @skip_python_older_312 def test_OMCPath_OMCProcessDocker(): - omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - om = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + assert omcs.sendExpression("getVersion()") == "OpenModelica 1.25.0" - _run_OMCPath_checks(om) + _run_OMCPath_checks(omcs) - del omcp - del om + del omcs @pytest.mark.skip(reason="Not able to run WSL on github") @skip_python_older_312 def test_OMCPath_OMCProcessWSL(): - omcp = OMPython.OMCSessionWSL( + omcs = OMPython.OMCSessionWSL( wsl_omc='omc', wsl_user='omc', timeout=30.0, ) - om = OMPython.OMCSessionZMQ(omc_process=omcp) - _run_OMCPath_checks(om) + _run_OMCPath_checks(omcs) - del omcp - del om + del omcs -def _run_OMCPath_checks(om: OMPython.OMCSessionZMQ): - p1 = om.omcpath_tempdir() +def _run_OMCPath_checks(omcs: OMPython.OMCSession): + p1 = omcs.omcpath_tempdir() p2 = p1 / 'test' p2.mkdir() assert p2.is_dir() @@ -81,14 +68,14 @@ def _run_OMCPath_checks(om: OMPython.OMCSessionZMQ): def test_OMCPath_write_file(tmpdir): - om = OMPython.OMCSessionZMQ() + omcs = OMPython.OMCSessionLocal() data = "abc # \\t # \" # \\n # xyz" - p1 = om.omcpath_tempdir() + p1 = omcs.omcpath_tempdir() p2 = p1 / 'test.txt' p2.write_text(data=data) assert data == p2.read_text() - del om + del omcs diff --git a/tests/test_OMSessionCmd.py b/tests/test_OMSessionCmd.py index bff4afde0..d3997ecf3 100644 --- a/tests/test_OMSessionCmd.py +++ b/tests/test_OMSessionCmd.py @@ -2,8 +2,8 @@ def test_isPackage(): - omczmq = OMPython.OMCSessionZMQ() - omccmd = OMPython.OMCSessionCmd(session=omczmq.omc_process) + omcs = OMPython.OMCSessionLocal() + omccmd = OMPython.OMCSessionCmd(session=omcs) assert not omccmd.isPackage('Modelica') diff --git a/tests/test_ZMQ.py b/tests/test_ZMQ.py index ba101560d..1302a79da 100644 --- a/tests/test_ZMQ.py +++ b/tests/test_ZMQ.py @@ -14,58 +14,55 @@ def model_time_str(): @pytest.fixture -def om(tmp_path): +def omcs(tmp_path): origDir = pathlib.Path.cwd() os.chdir(tmp_path) - om = OMPython.OMCSessionZMQ() + omcs = OMPython.OMCSessionLocal() os.chdir(origDir) - return om + return omcs -def testHelloWorld(om): - assert om.sendExpression('"HelloWorld!"') == "HelloWorld!" +def testHelloWorld(omcs): + assert omcs.sendExpression('"HelloWorld!"') == "HelloWorld!" -def test_Translate(om, model_time_str): - assert om.sendExpression(model_time_str) == ("M",) - assert om.sendExpression('translateModel(M)') is True +def test_Translate(omcs, model_time_str): + assert omcs.sendExpression(model_time_str) == ("M",) + assert omcs.sendExpression('translateModel(M)') is True -def test_Simulate(om, model_time_str): - assert om.sendExpression(f'loadString("{model_time_str}")') is True - om.sendExpression('res:=simulate(M, stopTime=2.0)') - assert om.sendExpression('res.resultFile') +def test_Simulate(omcs, model_time_str): + assert omcs.sendExpression(f'loadString("{model_time_str}")') is True + omcs.sendExpression('res:=simulate(M, stopTime=2.0)') + assert omcs.sendExpression('res.resultFile') -def test_execute(om): +def test_execute(omcs): with pytest.deprecated_call(): - assert om.execute('"HelloWorld!"') == '"HelloWorld!"\n' - assert om.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' - assert om.sendExpression('"HelloWorld!"', parsed=True) == 'HelloWorld!' + assert omcs.execute('"HelloWorld!"') == '"HelloWorld!"\n' + assert omcs.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' + assert omcs.sendExpression('"HelloWorld!"', parsed=True) == 'HelloWorld!' -def test_omcprocessport_execute(om): - port = om.omc_process.get_port() - omcp = OMPython.OMCSessionPort(omc_port=port) +def test_omcprocessport_execute(omcs): + port = omcs.get_port() + omcs2 = OMPython.OMCSessionPort(omc_port=port) # run 1 - om1 = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om1.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' + assert omcs.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' # run 2 - om2 = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om2.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' + assert omcs2.sendExpression('"HelloWorld!"', parsed=False) == '"HelloWorld!"\n' - del om1 - del om2 + del omcs2 -def test_omcprocessport_simulate(om, model_time_str): - port = om.omc_process.get_port() - omcp = OMPython.OMCSessionPort(omc_port=port) +def test_omcprocessport_simulate(omcs, model_time_str): + port = omcs.get_port() + omcs2 = OMPython.OMCSessionPort(omc_port=port) - om = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om.sendExpression(f'loadString("{model_time_str}")') is True - om.sendExpression('res:=simulate(M, stopTime=2.0)') - assert om.sendExpression('res.resultFile') != "" - del om + assert omcs2.sendExpression(f'loadString("{model_time_str}")') is True + omcs2.sendExpression('res:=simulate(M, stopTime=2.0)') + assert omcs2.sendExpression('res.resultFile') != "" + + del omcs2 diff --git a/tests/test_docker.py b/tests/test_docker.py index 025c48e31..f19735990 100644 --- a/tests/test_docker.py +++ b/tests/test_docker.py @@ -10,23 +10,17 @@ @skip_on_windows def test_docker(): - omcp = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") - om = OMPython.OMCSessionZMQ(omc_process=omcp) - assert om.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcs = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal") + assert omcs.sendExpression("getVersion()") == "OpenModelica 1.25.0" - omcpInner = OMPython.OMCSessionDockerContainer(dockerContainer=omcp.get_docker_container_id()) - omInner = OMPython.OMCSessionZMQ(omc_process=omcpInner) - assert omInner.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcsInner = OMPython.OMCSessionDockerContainer(dockerContainer=omcs.get_docker_container_id()) + assert omcsInner.sendExpression("getVersion()") == "OpenModelica 1.25.0" - omcp2 = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal", port=11111) - om2 = OMPython.OMCSessionZMQ(omc_process=omcp2) - assert om2.sendExpression("getVersion()") == "OpenModelica 1.25.0" + omcs2 = OMPython.OMCSessionDocker(docker="openmodelica/openmodelica:v1.25.0-minimal", port=11111) + assert omcs2.sendExpression("getVersion()") == "OpenModelica 1.25.0" - del omcp2 - del om2 + del omcs2 - del omcpInner - del omInner + del omcsInner - del omcp - del om + del omcs From 1c62641b2e9fdb171d04f2fda8cf46897e1b9f73 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 26 Nov 2025 20:21:33 +0100 Subject: [PATCH 2/7] [OMCSession*] define set_timeout() --- OMPython/OMCSession.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 861f2a3a1..46590c060 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -488,8 +488,6 @@ class OMCSessionRunData: cmd_model_executable: Optional[str] = None # additional library search path; this is mainly needed if OMCProcessLocal is run on Windows cmd_library_path: Optional[str] = None - # command timeout - cmd_timeout: Optional[float] = 10.0 # working directory to be used on the *local* system cmd_cwd_local: Optional[str] = None @@ -564,13 +562,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD """ return self.omc_process.omc_run_data_update(omc_run_data=omc_run_data) - @staticmethod - def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: + def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int: """ Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to keep instances of over classes around. """ - return OMCSession.run_model_executable(cmd_run_data=cmd_run_data) + return self.omc_process.run_model_executable(cmd_run_data=cmd_run_data) def execute(self, command: str): return self.omc_process.execute(command=command) @@ -727,6 +724,19 @@ def __del__(self): finally: self._omc_process = None + def set_timeout(self, timeout: Optional[float] = None) -> float: + """ + Set the timeout to be used for OMC communication (OMCSession). + + The defined value is set and the current value is returned. If None is provided as argument, nothing is changed. + """ + retval = self._timeout + if timeout is not None: + if timeout <= 0.0: + raise OMCSessionException(f"Invalid timeout value: {timeout}!") + self._timeout = timeout + return retval + @staticmethod def escape_str(value: str) -> str: """ @@ -778,11 +788,9 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath: return tempdir - @staticmethod - def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: + def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int: """ - Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to - keep instances of over classes around. + Run the command defined in cmd_run_data. """ my_env = os.environ.copy() @@ -799,7 +807,7 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int: text=True, env=my_env, cwd=cmd_run_data.cmd_cwd_local, - timeout=cmd_run_data.cmd_timeout, + timeout=self._timeout, check=True, ) stdout = cmdres.stdout.strip() From 9447ba202f793e5a1d0fd00a8a1c5fe764d5a7ee Mon Sep 17 00:00:00 2001 From: syntron Date: Tue, 25 Nov 2025 22:26:36 +0100 Subject: [PATCH 3/7] [OMCSession*] align all usages of timeout to the same structure --- OMPython/OMCSession.py | 113 +++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 46590c060..32ba5b957 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -839,34 +839,32 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any: Caller should only check for OMCSessionException. """ - # this is needed if the class is not fully initialized or in the process of deletion - if hasattr(self, '_timeout'): - timeout = self._timeout - else: - timeout = 1.0 - if self._omc_zmq is None: raise OMCSessionException("No OMC running. Please create a new instance of OMCSession!") logger.debug("sendExpression(%r, parsed=%r)", command, parsed) + MAX_RETRIES = 50 attempts = 0 - while True: + while attempts < MAX_RETRIES: + attempts += 1 + try: self._omc_zmq.send_string(str(command), flags=zmq.NOBLOCK) break except zmq.error.Again: pass - attempts += 1 - if attempts >= 50: - # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked - try: - log_content = self.get_log() - except OMCSessionException: - log_content = 'log not available' - raise OMCSessionException(f"No connection with OMC (timeout={timeout}). " - f"Log-file says: \n{log_content}") - time.sleep(timeout / 50.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked + try: + log_content = self.get_log() + except OMCSessionException: + log_content = 'log not available' + + logger.error(f"Docker did not start. Log-file says:\n{log_content}") + raise OMCSessionException(f"No connection with OMC (timeout={self._timeout}).") + if command == "quit()": self._omc_zmq.close() self._omc_zmq = None @@ -1095,25 +1093,23 @@ def _omc_port_get(self) -> str: port = None # See if the omc server is running + MAX_RETRIES = 80 attempts = 0 - while True: - omc_portfile_path = self._get_portfile_path() + while attempts < MAX_RETRIES: + attempts += 1 + omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None and omc_portfile_path.is_file(): # Read the port file with open(file=omc_portfile_path, mode='r', encoding="utf-8") as f_p: port = f_p.readline() break - if port is not None: break - - attempts += 1 - if attempts == 80.0: - raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}). " - f"Could not open file {omc_portfile_path}. " - f"Log-file says:\n{self.get_log()}") - time.sleep(self._timeout / 80.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}).") logger.info(f"Local OMC Server is up and running at ZMQ port {port} " f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}") @@ -1195,7 +1191,11 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: raise NotImplementedError("Docker not supported on win32!") docker_process = None - for _ in range(0, 40): + MAX_RETRIES = 40 + attempts = 0 + while attempts < MAX_RETRIES: + attempts += 1 + docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip() docker_process = None for line in docker_top.split("\n"): @@ -1206,10 +1206,12 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: except psutil.NoSuchProcess as ex: raise OMCSessionException(f"Could not find PID {docker_top} - " "is this a docker instance spawned without --pid=host?") from ex - if docker_process is not None: break - time.sleep(self._timeout / 40.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).") return docker_process @@ -1231,8 +1233,11 @@ def _omc_port_get(self) -> str: raise OMCSessionException(f"Invalid docker container ID: {self._docker_container_id}") # See if the omc server is running + MAX_RETRIES = 80 attempts = 0 - while True: + while attempts < MAX_RETRIES: + attempts += 1 + omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None: try: @@ -1243,16 +1248,12 @@ def _omc_port_get(self) -> str: port = output.decode().strip() except subprocess.CalledProcessError: pass - if port is not None: break - - attempts += 1 - if attempts == 80.0: - raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}). " - f"Could not open port file {omc_portfile_path}. " - f"Log-file says:\n{self.get_log()}") - time.sleep(self._timeout / 80.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).") logger.info(f"Docker based OMC Server is up and running at port {port}") @@ -1420,25 +1421,28 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]: raise OMCSessionException(f"Invalid content for docker container ID file path: {docker_cid_file}") docker_cid = None - for _ in range(0, 40): + MAX_RETRIES = 40 + attempts = 0 + while attempts < MAX_RETRIES: + attempts += 1 + try: with open(file=docker_cid_file, mode="r", encoding="utf-8") as fh: docker_cid = fh.read().strip() except IOError: pass - if docker_cid: + if docker_cid is not None: break - time.sleep(self._timeout / 40.0) - - if docker_cid is None: + time.sleep(self._timeout / MAX_RETRIES) + else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"Docker did not start (timeout={self._timeout} might be too short " "especially if you did not docker pull the image before this command).") docker_process = self._docker_process_get(docker_cid=docker_cid) if docker_process is None: - raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}. " - f"Log-file says:\n{self.get_log()}") + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}.") return omc_process, docker_process, docker_cid @@ -1594,8 +1598,11 @@ def _omc_port_get(self) -> str: port = None # See if the omc server is running + MAX_RETRIES = 80 attempts = 0 - while True: + while attempts < MAX_RETRIES: + attempts += 1 + try: omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None: @@ -1606,16 +1613,12 @@ def _omc_port_get(self) -> str: port = output.decode().strip() except subprocess.CalledProcessError: pass - if port is not None: break - - attempts += 1 - if attempts == 80.0: - raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}). " - f"Could not open port file {omc_portfile_path}. " - f"Log-file says:\n{self.get_log()}") - time.sleep(self._timeout / 80.0) + time.sleep(self._timeout / MAX_RETRIES) + else: + logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") + raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}).") logger.info(f"WSL based OMC Server is up and running at ZMQ port {port} " f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}") From eed1f2d48d367132f4a56ae59cdcf89cf5ea7fc8 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 26 Nov 2025 19:38:48 +0100 Subject: [PATCH 4/7] [OMCSession*] simplify code for timeout loops --- OMPython/OMCSession.py | 73 +++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 32ba5b957..b670e9283 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -724,6 +724,31 @@ def __del__(self): finally: self._omc_process = None + def _timeout_loop( + self, + timeout: Optional[float] = None, + timestep: float = 0.1, + ): + """ + Helper (using yield) for while loops to check OMC startup / response. The loop is executed as long as True is + returned, i.e. the first False will stop the while loop. + """ + + if timeout is None: + timeout = self._timeout + if timeout <= 0: + raise OMCSessionException(f"Invalid timeout: {timeout}") + + timer = 0.0 + yield True + while True: + timer += timestep + if timer > timeout: + break + time.sleep(timestep) + yield True + yield False + def set_timeout(self, timeout: Optional[float] = None) -> float: """ Set the timeout to be used for OMC communication (OMCSession). @@ -844,17 +869,13 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any: logger.debug("sendExpression(%r, parsed=%r)", command, parsed) - MAX_RETRIES = 50 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.05) + while next(loop): try: self._omc_zmq.send_string(str(command), flags=zmq.NOBLOCK) break except zmq.error.Again: pass - time.sleep(self._timeout / MAX_RETRIES) else: # in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked try: @@ -1093,11 +1114,8 @@ def _omc_port_get(self) -> str: port = None # See if the omc server is running - MAX_RETRIES = 80 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.1) + while next(loop): omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None and omc_portfile_path.is_file(): # Read the port file @@ -1106,7 +1124,6 @@ def _omc_port_get(self) -> str: break if port is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}).") @@ -1191,11 +1208,8 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: raise NotImplementedError("Docker not supported on win32!") docker_process = None - MAX_RETRIES = 40 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.2) + while next(loop): docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip() docker_process = None for line in docker_top.split("\n"): @@ -1208,7 +1222,6 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: "is this a docker instance spawned without --pid=host?") from ex if docker_process is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).") @@ -1233,11 +1246,8 @@ def _omc_port_get(self) -> str: raise OMCSessionException(f"Invalid docker container ID: {self._docker_container_id}") # See if the omc server is running - MAX_RETRIES = 80 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.1) + while next(loop): omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None: try: @@ -1250,7 +1260,6 @@ def _omc_port_get(self) -> str: pass if port is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).") @@ -1421,11 +1430,8 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]: raise OMCSessionException(f"Invalid content for docker container ID file path: {docker_cid_file}") docker_cid = None - MAX_RETRIES = 40 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.1) + while next(loop): try: with open(file=docker_cid_file, mode="r", encoding="utf-8") as fh: docker_cid = fh.read().strip() @@ -1433,7 +1439,6 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]: pass if docker_cid is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"Docker did not start (timeout={self._timeout} might be too short " @@ -1598,11 +1603,8 @@ def _omc_port_get(self) -> str: port = None # See if the omc server is running - MAX_RETRIES = 80 - attempts = 0 - while attempts < MAX_RETRIES: - attempts += 1 - + loop = self._timeout_loop(timestep=0.1) + while next(loop): try: omc_portfile_path = self._get_portfile_path() if omc_portfile_path is not None: @@ -1615,7 +1617,6 @@ def _omc_port_get(self) -> str: pass if port is not None: break - time.sleep(self._timeout / MAX_RETRIES) else: logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}") raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}).") From 9980afbd2f619e0a135b30efca93653f9cc56371 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 10:21:54 +0100 Subject: [PATCH 5/7] [OMCSession] fix definiton of _timeout variable - use set_timeout() checks --- OMPython/OMCSession.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index b670e9283..396fb9e4f 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -648,7 +648,9 @@ def __init__( """ # store variables - self._timeout = timeout + # set_timeout() is used to define the value of _timeout as it includes additional checks + self._timeout: float + self.set_timeout(timeout=timeout) # generate a random string for this instance of OMC self._random_string = uuid.uuid4().hex # get a temporary directory From 0e42f28b12541c1b1e5684a20ee7b4b13da23cf2 Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 10:08:25 +0100 Subject: [PATCH 6/7] [OMCSession*] some additional cleanup (mypy / flake8) * remove not needed variable definitions * fix if condition for bool --- OMPython/OMCSession.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 396fb9e4f..c487b758e 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -983,7 +983,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any: raise OMCSessionException(f"OMC error occurred for 'sendExpression({command}, {parsed}):\n" f"{msg_long_str}") - if parsed is False: + if not parsed: return result try: @@ -1209,7 +1209,6 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]: if sys.platform == 'win32': raise NotImplementedError("Docker not supported on win32!") - docker_process = None loop = self._timeout_loop(timestep=0.2) while next(loop): docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip() @@ -1601,7 +1600,6 @@ def _omc_process_get(self) -> subprocess.Popen: return omc_process def _omc_port_get(self) -> str: - omc_portfile_path: Optional[pathlib.Path] = None port = None # See if the omc server is running From 982f13914d853f3e6fbd455782391f97cfe3f63a Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 27 Nov 2025 21:19:13 +0100 Subject: [PATCH 7/7] [OMCSession] move call to set_timeout() to __post_init__ --- OMPython/OMCSession.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index c487b758e..e1d1f1233 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -648,9 +648,7 @@ def __init__( """ # store variables - # set_timeout() is used to define the value of _timeout as it includes additional checks - self._timeout: float - self.set_timeout(timeout=timeout) + self._timeout = timeout # generate a random string for this instance of OMC self._random_string = uuid.uuid4().hex # get a temporary directory @@ -684,6 +682,9 @@ def __post_init__(self) -> None: """ Create the connection to the OMC server using ZeroMQ. """ + # set_timeout() is used to define the value of _timeout as it includes additional checks + self.set_timeout(timeout=self._timeout) + port = self.get_port() if not isinstance(port, str): raise OMCSessionException(f"Invalid content for port: {port}")