Skip to content

Commit a12c151

Browse files
committed
Merge branch 'ModelicaSystem_timeout' into v4.1.0-status20260123
2 parents c6e6f9d + 4e81487 commit a12c151

1 file changed

Lines changed: 86 additions & 73 deletions

File tree

OMPython/OMCSession.py

Lines changed: 86 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,6 @@ class OMCSessionRunData:
482482
cmd_model_executable: Optional[str] = None
483483
# additional library search path; this is mainly needed if OMCProcessLocal is run on Windows
484484
cmd_library_path: Optional[str] = None
485-
# command timeout
486-
cmd_timeout: Optional[float] = 10.0
487485

488486
# working directory to be used on the *local* system
489487
cmd_cwd_local: Optional[str] = None
@@ -558,13 +556,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
558556
"""
559557
return self.omc_process.omc_run_data_update(omc_run_data=omc_run_data)
560558

561-
@staticmethod
562-
def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
559+
def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int:
563560
"""
564561
Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
565562
keep instances of over classes around.
566563
"""
567-
return OMCSession.run_model_executable(cmd_run_data=cmd_run_data)
564+
return self.omc_process.run_model_executable(cmd_run_data=cmd_run_data)
568565

569566
def execute(self, command: str):
570567
return self.omc_process.execute(command=command)
@@ -679,6 +676,9 @@ def __post_init__(self) -> None:
679676
"""
680677
Create the connection to the OMC server using ZeroMQ.
681678
"""
679+
# set_timeout() is used to define the value of _timeout as it includes additional checks
680+
self.set_timeout(timeout=self._timeout)
681+
682682
port = self.get_port()
683683
if not isinstance(port, str):
684684
raise OMCSessionException(f"Invalid content for port: {port}")
@@ -721,6 +721,44 @@ def __del__(self):
721721
finally:
722722
self._omc_process = None
723723

724+
def _timeout_loop(
725+
self,
726+
timeout: Optional[float] = None,
727+
timestep: float = 0.1,
728+
):
729+
"""
730+
Helper (using yield) for while loops to check OMC startup / response. The loop is executed as long as True is
731+
returned, i.e. the first False will stop the while loop.
732+
"""
733+
734+
if timeout is None:
735+
timeout = self._timeout
736+
if timeout <= 0:
737+
raise OMCSessionException(f"Invalid timeout: {timeout}")
738+
739+
timer = 0.0
740+
yield True
741+
while True:
742+
timer += timestep
743+
if timer > timeout:
744+
break
745+
time.sleep(timestep)
746+
yield True
747+
yield False
748+
749+
def set_timeout(self, timeout: Optional[float] = None) -> float:
750+
"""
751+
Set the timeout to be used for OMC communication (OMCSession).
752+
753+
The defined value is set and the current value is returned. If None is provided as argument, nothing is changed.
754+
"""
755+
retval = self._timeout
756+
if timeout is not None:
757+
if timeout <= 0.0:
758+
raise OMCSessionException(f"Invalid timeout value: {timeout}!")
759+
self._timeout = timeout
760+
return retval
761+
724762
@staticmethod
725763
def escape_str(value: str) -> str:
726764
"""
@@ -772,11 +810,9 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
772810

773811
return tempdir
774812

775-
@staticmethod
776-
def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
813+
def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int:
777814
"""
778-
Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
779-
keep instances of over classes around.
815+
Run the command defined in cmd_run_data.
780816
"""
781817

782818
my_env = os.environ.copy()
@@ -793,8 +829,8 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
793829
text=True,
794830
env=my_env,
795831
cwd=cmd_run_data.cmd_cwd_local,
796-
timeout=cmd_run_data.cmd_timeout,
797-
check=False,
832+
timeout=self._timeout,
833+
check=True,
798834
)
799835
stdout = cmdres.stdout.strip()
800836
stderr = cmdres.stderr.strip()
@@ -830,34 +866,28 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
830866
Caller should only check for OMCSessionException.
831867
"""
832868

833-
# this is needed if the class is not fully initialized or in the process of deletion
834-
if hasattr(self, '_timeout'):
835-
timeout = self._timeout
836-
else:
837-
timeout = 1.0
838-
839869
if self._omc_zmq is None:
840870
raise OMCSessionException("No OMC running. Please create a new instance of OMCSession!")
841871

842872
logger.debug("sendExpression(%r, parsed=%r)", command, parsed)
843873

844-
attempts = 0
845-
while True:
874+
loop = self._timeout_loop(timestep=0.05)
875+
while next(loop):
846876
try:
847877
self._omc_zmq.send_string(str(command), flags=zmq.NOBLOCK)
848878
break
849879
except zmq.error.Again:
850880
pass
851-
attempts += 1
852-
if attempts >= 50:
853-
# in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
854-
try:
855-
log_content = self.get_log()
856-
except OMCSessionException:
857-
log_content = 'log not available'
858-
raise OMCSessionException(f"No connection with OMC (timeout={timeout}). "
859-
f"Log-file says: \n{log_content}")
860-
time.sleep(timeout / 50.0)
881+
else:
882+
# in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
883+
try:
884+
log_content = self.get_log()
885+
except OMCSessionException:
886+
log_content = 'log not available'
887+
888+
logger.error(f"OMC did not start. Log-file says:\n{log_content}")
889+
raise OMCSessionException(f"No connection with OMC (timeout={self._timeout}).")
890+
861891
if command == "quit()":
862892
self._omc_zmq.close()
863893
self._omc_zmq = None
@@ -953,7 +983,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
953983
raise OMCSessionException(f"OMC error occurred for 'sendExpression({command}, {parsed}):\n"
954984
f"{msg_long_str}")
955985

956-
if parsed is False:
986+
if not parsed:
957987
return result
958988

959989
try:
@@ -1102,25 +1132,19 @@ def _omc_port_get(self) -> str:
11021132
port = None
11031133

11041134
# See if the omc server is running
1105-
attempts = 0
1106-
while True:
1135+
loop = self._timeout_loop(timestep=0.1)
1136+
while next(loop):
11071137
omc_portfile_path = self._get_portfile_path()
1108-
11091138
if omc_portfile_path is not None and omc_portfile_path.is_file():
11101139
# Read the port file
11111140
with open(file=omc_portfile_path, mode='r', encoding="utf-8") as f_p:
11121141
port = f_p.readline()
11131142
break
1114-
11151143
if port is not None:
11161144
break
1117-
1118-
attempts += 1
1119-
if attempts == 80.0:
1120-
raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}). "
1121-
f"Could not open file {omc_portfile_path}. "
1122-
f"Log-file says:\n{self.get_log()}")
1123-
time.sleep(self._timeout / 80.0)
1145+
else:
1146+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1147+
raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}).")
11241148

11251149
logger.info(f"Local OMC Server is up and running at ZMQ port {port} "
11261150
f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}")
@@ -1201,8 +1225,8 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
12011225
if sys.platform == 'win32':
12021226
raise NotImplementedError("Docker not supported on win32!")
12031227

1204-
docker_process = None
1205-
for _ in range(0, 40):
1228+
loop = self._timeout_loop(timestep=0.2)
1229+
while next(loop):
12061230
docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip()
12071231
docker_process = None
12081232
for line in docker_top.split("\n"):
@@ -1213,10 +1237,11 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
12131237
except psutil.NoSuchProcess as ex:
12141238
raise OMCSessionException(f"Could not find PID {docker_top} - "
12151239
"is this a docker instance spawned without --pid=host?") from ex
1216-
12171240
if docker_process is not None:
12181241
break
1219-
time.sleep(self._timeout / 40.0)
1242+
else:
1243+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1244+
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).")
12201245

12211246
return docker_process
12221247

@@ -1238,8 +1263,8 @@ def _omc_port_get(self) -> str:
12381263
raise OMCSessionException(f"Invalid docker container ID: {self._docker_container_id}")
12391264

12401265
# See if the omc server is running
1241-
attempts = 0
1242-
while True:
1266+
loop = self._timeout_loop(timestep=0.1)
1267+
while next(loop):
12431268
omc_portfile_path = self._get_portfile_path()
12441269
if omc_portfile_path is not None:
12451270
try:
@@ -1250,16 +1275,11 @@ def _omc_port_get(self) -> str:
12501275
port = output.decode().strip()
12511276
except subprocess.CalledProcessError:
12521277
pass
1253-
12541278
if port is not None:
12551279
break
1256-
1257-
attempts += 1
1258-
if attempts == 80.0:
1259-
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}). "
1260-
f"Could not open port file {omc_portfile_path}. "
1261-
f"Log-file says:\n{self.get_log()}")
1262-
time.sleep(self._timeout / 80.0)
1280+
else:
1281+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1282+
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).")
12631283

12641284
logger.info(f"Docker based OMC Server is up and running at port {port}")
12651285

@@ -1427,25 +1447,24 @@ def _docker_omc_start(self) -> Tuple[subprocess.Popen, DockerPopen, str]:
14271447
raise OMCSessionException(f"Invalid content for docker container ID file path: {docker_cid_file}")
14281448

14291449
docker_cid = None
1430-
for _ in range(0, 40):
1450+
loop = self._timeout_loop(timestep=0.1)
1451+
while next(loop):
14311452
try:
14321453
with open(file=docker_cid_file, mode="r", encoding="utf-8") as fh:
14331454
docker_cid = fh.read().strip()
14341455
except IOError:
14351456
pass
1436-
if docker_cid:
1457+
if docker_cid is not None:
14371458
break
1438-
time.sleep(self._timeout / 40.0)
1439-
1440-
if docker_cid is None:
1459+
else:
14411460
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
14421461
raise OMCSessionException(f"Docker did not start (timeout={self._timeout} might be too short "
14431462
"especially if you did not docker pull the image before this command).")
14441463

14451464
docker_process = self._docker_process_get(docker_cid=docker_cid)
14461465
if docker_process is None:
1447-
raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}. "
1448-
f"Log-file says:\n{self.get_log()}")
1466+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1467+
raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}.")
14491468

14501469
return omc_process, docker_process, docker_cid
14511470

@@ -1597,12 +1616,11 @@ def _omc_process_get(self) -> subprocess.Popen:
15971616
return omc_process
15981617

15991618
def _omc_port_get(self) -> str:
1600-
omc_portfile_path: Optional[pathlib.Path] = None
16011619
port = None
16021620

16031621
# See if the omc server is running
1604-
attempts = 0
1605-
while True:
1622+
loop = self._timeout_loop(timestep=0.1)
1623+
while next(loop):
16061624
try:
16071625
omc_portfile_path = self._get_portfile_path()
16081626
if omc_portfile_path is not None:
@@ -1613,16 +1631,11 @@ def _omc_port_get(self) -> str:
16131631
port = output.decode().strip()
16141632
except subprocess.CalledProcessError:
16151633
pass
1616-
16171634
if port is not None:
16181635
break
1619-
1620-
attempts += 1
1621-
if attempts == 80.0:
1622-
raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}). "
1623-
f"Could not open port file {omc_portfile_path}. "
1624-
f"Log-file says:\n{self.get_log()}")
1625-
time.sleep(self._timeout / 80.0)
1636+
else:
1637+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1638+
raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}).")
16261639

16271640
logger.info(f"WSL based OMC Server is up and running at ZMQ port {port} "
16281641
f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}")

0 commit comments

Comments
 (0)