Skip to content

Commit b1aba64

Browse files
committed
Merge branch 'ModelicaSystem_timeout' into v4.1.0-syntron
2 parents 304e117 + 223c895 commit b1aba64

File tree

1 file changed

+85
-72
lines changed

1 file changed

+85
-72
lines changed

OMPython/OMCSession.py

Lines changed: 85 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -517,8 +517,6 @@ class OMCSessionRunData:
517517
cmd_model_executable: Optional[str] = None
518518
# additional library search path; this is mainly needed if OMCProcessLocal is run on Windows
519519
cmd_library_path: Optional[str] = None
520-
# command timeout
521-
cmd_timeout: Optional[float] = 10.0
522520

523521
# working directory to be used on the *local* system
524522
cmd_cwd_local: Optional[str] = None
@@ -593,13 +591,12 @@ def omc_run_data_update(self, omc_run_data: OMCSessionRunData) -> OMCSessionRunD
593591
"""
594592
return self.omc_process.omc_run_data_update(omc_run_data=omc_run_data)
595593

596-
@staticmethod
597-
def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
594+
def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int:
598595
"""
599596
Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
600597
keep instances of over classes around.
601598
"""
602-
return OMCSession.run_model_executable(cmd_run_data=cmd_run_data)
599+
return self.omc_process.run_model_executable(cmd_run_data=cmd_run_data)
603600

604601
def execute(self, command: str):
605602
return self.omc_process.execute(command=command)
@@ -714,6 +711,9 @@ def __post_init__(self) -> None:
714711
"""
715712
Create the connection to the OMC server using ZeroMQ.
716713
"""
714+
# set_timeout() is used to define the value of _timeout as it includes additional checks
715+
self.set_timeout(timeout=self._timeout)
716+
717717
port = self.get_port()
718718
if not isinstance(port, str):
719719
raise OMCSessionException(f"Invalid content for port: {port}")
@@ -756,6 +756,44 @@ def __del__(self):
756756
finally:
757757
self._omc_process = None
758758

759+
def _timeout_loop(
760+
self,
761+
timeout: Optional[float] = None,
762+
timestep: float = 0.1,
763+
):
764+
"""
765+
Helper (using yield) for while loops to check OMC startup / response. The loop is executed as long as True is
766+
returned, i.e. the first False will stop the while loop.
767+
"""
768+
769+
if timeout is None:
770+
timeout = self._timeout
771+
if timeout <= 0:
772+
raise OMCSessionException(f"Invalid timeout: {timeout}")
773+
774+
timer = 0.0
775+
yield True
776+
while True:
777+
timer += timestep
778+
if timer > timeout:
779+
break
780+
time.sleep(timestep)
781+
yield True
782+
yield False
783+
784+
def set_timeout(self, timeout: Optional[float] = None) -> float:
785+
"""
786+
Set the timeout to be used for OMC communication (OMCSession).
787+
788+
The defined value is set and the current value is returned. If None is provided as argument, nothing is changed.
789+
"""
790+
retval = self._timeout
791+
if timeout is not None:
792+
if timeout <= 0.0:
793+
raise OMCSessionException(f"Invalid timeout value: {timeout}!")
794+
self._timeout = timeout
795+
return retval
796+
759797
@staticmethod
760798
def escape_str(value: str) -> str:
761799
"""
@@ -807,11 +845,9 @@ def omcpath_tempdir(self, tempdir_base: Optional[OMCPath] = None) -> OMCPath:
807845

808846
return tempdir
809847

810-
@staticmethod
811-
def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
848+
def run_model_executable(self, cmd_run_data: OMCSessionRunData) -> int:
812849
"""
813-
Run the command defined in cmd_run_data. This class is defined as static method such that there is no need to
814-
keep instances of over classes around.
850+
Run the command defined in cmd_run_data.
815851
"""
816852

817853
my_env = os.environ.copy()
@@ -828,7 +864,7 @@ def run_model_executable(cmd_run_data: OMCSessionRunData) -> int:
828864
text=True,
829865
env=my_env,
830866
cwd=cmd_run_data.cmd_cwd_local,
831-
timeout=cmd_run_data.cmd_timeout,
867+
timeout=self._timeout,
832868
check=True,
833869
)
834870
stdout = cmdres.stdout.strip()
@@ -862,34 +898,28 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
862898
Caller should only check for OMCSessionException.
863899
"""
864900

865-
# this is needed if the class is not fully initialized or in the process of deletion
866-
if hasattr(self, '_timeout'):
867-
timeout = self._timeout
868-
else:
869-
timeout = 1.0
870-
871901
if self._omc_zmq is None:
872902
raise OMCSessionException("No OMC running. Please create a new instance of OMCSession!")
873903

874904
logger.debug("sendExpression(%r, parsed=%r)", command, parsed)
875905

876-
attempts = 0
877-
while True:
906+
loop = self._timeout_loop(timestep=0.05)
907+
while next(loop):
878908
try:
879909
self._omc_zmq.send_string(str(command), flags=zmq.NOBLOCK)
880910
break
881911
except zmq.error.Again:
882912
pass
883-
attempts += 1
884-
if attempts >= 50:
885-
# in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
886-
try:
887-
log_content = self.get_log()
888-
except OMCSessionException:
889-
log_content = 'log not available'
890-
raise OMCSessionException(f"No connection with OMC (timeout={timeout}). "
891-
f"Log-file says: \n{log_content}")
892-
time.sleep(timeout / 50.0)
913+
else:
914+
# in the deletion process, the content is cleared. Thus, any access to a class attribute must be checked
915+
try:
916+
log_content = self.get_log()
917+
except OMCSessionException:
918+
log_content = 'log not available'
919+
920+
logger.error(f"Docker did not start. Log-file says:\n{log_content}")
921+
raise OMCSessionException(f"No connection with OMC (timeout={self._timeout}).")
922+
893923
if command == "quit()":
894924
self._omc_zmq.close()
895925
self._omc_zmq = None
@@ -985,7 +1015,7 @@ def sendExpression(self, command: str, parsed: bool = True) -> Any:
9851015
raise OMCSessionException(f"OMC error occurred for 'sendExpression({command}, {parsed}):\n"
9861016
f"{msg_long_str}")
9871017

988-
if parsed is False:
1018+
if not parsed:
9891019
return result
9901020

9911021
try:
@@ -1134,25 +1164,19 @@ def _omc_port_get(self) -> str:
11341164
port = None
11351165

11361166
# See if the omc server is running
1137-
attempts = 0
1138-
while True:
1167+
loop = self._timeout_loop(timestep=0.1)
1168+
while next(loop):
11391169
omc_portfile_path = self._get_portfile_path()
1140-
11411170
if omc_portfile_path is not None and omc_portfile_path.is_file():
11421171
# Read the port file
11431172
with open(file=omc_portfile_path, mode='r', encoding="utf-8") as f_p:
11441173
port = f_p.readline()
11451174
break
1146-
11471175
if port is not None:
11481176
break
1149-
1150-
attempts += 1
1151-
if attempts == 80.0:
1152-
raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}). "
1153-
f"Could not open file {omc_portfile_path}. "
1154-
f"Log-file says:\n{self.get_log()}")
1155-
time.sleep(self._timeout / 80.0)
1177+
else:
1178+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1179+
raise OMCSessionException(f"OMC Server did not start (timeout={self._timeout}).")
11561180

11571181
logger.info(f"Local OMC Server is up and running at ZMQ port {port} "
11581182
f"pid={self._omc_process.pid if isinstance(self._omc_process, subprocess.Popen) else '?'}")
@@ -1233,8 +1257,8 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
12331257
if sys.platform == 'win32':
12341258
raise NotImplementedError("Docker not supported on win32!")
12351259

1236-
docker_process = None
1237-
for _ in range(0, 40):
1260+
loop = self._timeout_loop(timestep=0.2)
1261+
while next(loop):
12381262
docker_top = subprocess.check_output(["docker", "top", docker_cid]).decode().strip()
12391263
docker_process = None
12401264
for line in docker_top.split("\n"):
@@ -1245,10 +1269,11 @@ def _docker_process_get(self, docker_cid: str) -> Optional[DockerPopen]:
12451269
except psutil.NoSuchProcess as ex:
12461270
raise OMCSessionException(f"Could not find PID {docker_top} - "
12471271
"is this a docker instance spawned without --pid=host?") from ex
1248-
12491272
if docker_process is not None:
12501273
break
1251-
time.sleep(self._timeout / 40.0)
1274+
else:
1275+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1276+
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).")
12521277

12531278
return docker_process
12541279

@@ -1270,8 +1295,8 @@ def _omc_port_get(self) -> str:
12701295
raise OMCSessionException(f"Invalid docker container ID: {self._docker_container_id}")
12711296

12721297
# See if the omc server is running
1273-
attempts = 0
1274-
while True:
1298+
loop = self._timeout_loop(timestep=0.1)
1299+
while next(loop):
12751300
omc_portfile_path = self._get_portfile_path()
12761301
if omc_portfile_path is not None:
12771302
try:
@@ -1282,16 +1307,11 @@ def _omc_port_get(self) -> str:
12821307
port = output.decode().strip()
12831308
except subprocess.CalledProcessError:
12841309
pass
1285-
12861310
if port is not None:
12871311
break
1288-
1289-
attempts += 1
1290-
if attempts == 80.0:
1291-
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}). "
1292-
f"Could not open port file {omc_portfile_path}. "
1293-
f"Log-file says:\n{self.get_log()}")
1294-
time.sleep(self._timeout / 80.0)
1312+
else:
1313+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1314+
raise OMCSessionException(f"Docker based OMC Server did not start (timeout={self._timeout}).")
12951315

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

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

14611481
docker_cid = None
1462-
for _ in range(0, 40):
1482+
loop = self._timeout_loop(timestep=0.1)
1483+
while next(loop):
14631484
try:
14641485
with open(file=docker_cid_file, mode="r", encoding="utf-8") as fh:
14651486
docker_cid = fh.read().strip()
14661487
except IOError:
14671488
pass
1468-
if docker_cid:
1489+
if docker_cid is not None:
14691490
break
1470-
time.sleep(self._timeout / 40.0)
1471-
1472-
if docker_cid is None:
1491+
else:
14731492
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
14741493
raise OMCSessionException(f"Docker did not start (timeout={self._timeout} might be too short "
14751494
"especially if you did not docker pull the image before this command).")
14761495

14771496
docker_process = self._docker_process_get(docker_cid=docker_cid)
14781497
if docker_process is None:
1479-
raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}. "
1480-
f"Log-file says:\n{self.get_log()}")
1498+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1499+
raise OMCSessionException(f"Docker top did not contain omc process {self._random_string}.")
14811500

14821501
return omc_process, docker_process, docker_cid
14831502

@@ -1629,12 +1648,11 @@ def _omc_process_get(self) -> subprocess.Popen:
16291648
return omc_process
16301649

16311650
def _omc_port_get(self) -> str:
1632-
omc_portfile_path: Optional[pathlib.Path] = None
16331651
port = None
16341652

16351653
# See if the omc server is running
1636-
attempts = 0
1637-
while True:
1654+
loop = self._timeout_loop(timestep=0.1)
1655+
while next(loop):
16381656
try:
16391657
omc_portfile_path = self._get_portfile_path()
16401658
if omc_portfile_path is not None:
@@ -1645,16 +1663,11 @@ def _omc_port_get(self) -> str:
16451663
port = output.decode().strip()
16461664
except subprocess.CalledProcessError:
16471665
pass
1648-
16491666
if port is not None:
16501667
break
1651-
1652-
attempts += 1
1653-
if attempts == 80.0:
1654-
raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}). "
1655-
f"Could not open port file {omc_portfile_path}. "
1656-
f"Log-file says:\n{self.get_log()}")
1657-
time.sleep(self._timeout / 80.0)
1668+
else:
1669+
logger.error(f"Docker did not start. Log-file says:\n{self.get_log()}")
1670+
raise OMCSessionException(f"WSL based OMC Server did not start (timeout={self._timeout}).")
16581671

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

0 commit comments

Comments
 (0)